Just like abstract classes, we use an interface to implement abstraction in Java. It plays a crucial role in scenarios when multiple classes need to follow a common set of rules or provide a specific set of behaviours.
In this blog, we will discuss various properties, advantages and use cases of interfaces in Java.
In object-oriented programming, abstraction simplifies the representation of complex systems by focusing on essential features and hiding implementation details. So, interfaces provide a way to achieve abstraction in Java by separating the definition of behaviour from its implementation.
An interface is a collection of abstract methods (methods without an implementation) and all the implementing classes must provide an implementation for these methods.
public interface Animal {
// Declaration of abstract method
void makeSound();
void eat();
// Constant declaration
String CATEGORY = "Animals";
// Default method with implementation
default void sleep() {
System.out.println("The animal is sleeping");
}
// Static method
static void info() {
System.out.println("This is an Animal interface");
}
}
To use an interface, a class must implement it using the implements keyword.
public class Dog implements Animal {
// Implementing the abstract methods
public void makeSound() {
System.out.println("The dog barks");
}
public void eat() {
System.out.println("The dog is eating");
}
// Overriding the default method
public void sleep() {
System.out.println("The dog is sleeping");
}
}
Interfaces can extend other interfaces using the extends keyword. This will help us to create more specialized interfaces that build upon the functionality of their parent interfaces.
public interface Animal {
void makeSound();
}
public interface Bird extends Animal {
void fly();
}
public class Eagle implements Bird {
public void makeSound() {
System.out.println("The eagle screeches");
}
public void fly() {
System.out.println("The eagle is flying");
}
}
In Java, a class can implement multiple interfaces by using the implements keyword followed by a comma-separated list of interface names. Due to this, it inherits the method declarations from each interface and must provide concrete implementations for all of them.
As we know, Java doesn't support multiple Inheritance because one class can extend only one class (Why? Explore and think). But we can use interfaces to achieve the same purpose because a class can extend two or more interfaces.
public interface Interface1 {
void method1();
}
public interface Interface2 {
void method2();
}
public class MyClass implements Interface1, Interface2 {
@Override
public void method1() {
// Implementation for method1
}
@Override
public void method2() {
// Implementation for method2
}
}
Here is another example in which we use interfaces for multiple inheritance by using default methods. Here MyClass class implements both interfaces and overrides the default method. To resolve the conflict and specify which default method implementation should be invoked, we are explicitly calling the default methods from the interfaces.
interface Interface1 {
default void method() {
System.out.println("Interface1 method call");
}
}
interface Interface2 {
default void method() {
System.out.println("Interface2 method call");
}
}
class MyClass implements Interface1, Interface2 {
public void method() {
// Invoke the method()from Interface1
Interface1.super.method();
// Invoke the method() from Interface2
Interface2.super.method();
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.method();
}
}
If a class wants to extend another class and implement multiple interfaces, the extends clause specifying the superclass comes before the implements clause for the interfaces.
In Java, we can use the interface as a reference data type just like any other class. But a reference of interface type must be assigned with an instance of a class that implements that interface.
The above idea will help us to implement runtime polymorphism in Java. For this, we create an interface reference and dynamically assign objects of different classes that implement the same interface at runtime. Let's understand this via an example:
public interface Animal {
void sound();
}
public class Dog implements Animal {
public void sound() {
System.out.println("The dog barks.");
}
}
public class Cat implements Animal {
public void sound() {
System.out.println("The cat meows.");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.sound(); // Calls the sound method of Dog class
cat.sound(); // Calls the sound method of Cat class
}
}
We declared the objects of Dog and Cat classes as Animal types. This allows us to treat both objects as generic Animal objects, regardless of their specific class. At runtime, when the makeSound() method is called on the Animal objects, the JVM determines the actual class of the object and executes the corresponding implementation of the method.
By using the above idea, we can easily add new classes that implement the Animal interface without modifying the existing code. Now consider this code example:
public interface Animal {
void sound();
}
public class Dog implements Animal{
public void name(){
System.out.println("JIMMY");
}
//implementing method sound()
public void sound(){
System.out.println("BARK");
}
}
public class Test {
public static final void main(String[] args) {
Animal animal = new Dog();
animal.sound();
animal.name();
}
}
In the above code, animal.name() will cause a compilation error because the Animal reference (animal) does not have a name() method defined in the interface. Even though the object being referenced is an instance of the Dog class, the reference type determines which methods can be accessed. To use the name() method, we have to use the reference of Dog type.
public static final void main(String[] args) {
Animal animal = new Dog();
animal.sound();
Dog dog = (Dog)(animal);//This is type casting.
dog.name();
dog.sound();
}
Many times, separate groups of developers have to agree on a basic reference for how their software interacts. Ideally, each group should be able to write their code without knowledge of the other group's code. This is where interfaces come into play in defining such contracts.
For example, let's consider cars being driven by software. Automobile manufacturers write software to operate any car, which includes operations like stopping, turning left, turning right, starting, etc. Now, let's assume you have knowledge of machine learning and have written algorithms for a self-driving car. However, to make the car drive, you need to know how to make it stop, turn in either direction, start, etc.
So, there is a need for a common interface between the two groups: manufacturers and the driving unit. The manufacturers must provide the interface that specifies methods that can be invoked to operate the car. The driving unit then writes software using these interface methods according to their requirements. This way, neither group needs to know how the other group is writing its code.
Imagine that in the future, the manufacturing team of the self-driving car has to add some features to their interface. How can we do this? Here are three possible ways:
Can we think to add new features using some other approach? Explore and think!
Thanks to Ankit Nishad for his contribution in creating the first version of this content. If you have any queries or feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy OOP!
Subscribe to get well designed content on data structure and algorithms, machine learning, system design, object orientd programming and math.
©2023 Code Algorithms Pvt. Ltd.
All rights reserved.