Just likeabstract classes, we use an interface to implementabstraction 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 ofabstract methods (methods without an implementation) and all the implementing classes must provide an implementation for these methods.
publicinterfaceAnimal{// Declaration of abstract methodvoidmakeSound();voideat();// Constant declarationStringCATEGORY="Animals";// Default method with implementationdefaultvoidsleep(){System.out.println("The animal is sleeping");}// Static methodstaticvoidinfo(){System.out.println("This is an Animal interface");}}
To use an interface, a class must implement it using theimplements keyword.
publicclassDogimplementsAnimal{// Implementing the abstract methodspublicvoidmakeSound(){System.out.println("The dog barks");}publicvoideat(){System.out.println("The dog is eating");}// Overriding the default methodpublicvoidsleep(){System.out.println("The dog is sleeping");}}
Interfaces can extend other interfaces using theextends keyword. This will help us to create more specialized interfaces that build upon the functionality of their parent interfaces.
publicinterfaceAnimal{voidmakeSound();}publicinterfaceBirdextendsAnimal{voidfly();}publicclassEagleimplementsBird{publicvoidmakeSound(){System.out.println("The eagle screeches");}publicvoidfly(){System.out.println("The eagle is flying");}}
In Java, a class can implement multiple interfaces by using theimplements 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 supportmultiple 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.
publicinterfaceInterface1{voidmethod1();}publicinterfaceInterface2{voidmethod2();}publicclassMyClassimplementsInterface1,Interface2{@Overridepublicvoidmethod1(){// Implementation for method1}@Overridepublicvoidmethod2(){// 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.
interfaceInterface1{defaultvoidmethod(){System.out.println("Interface1 method call");}}interfaceInterface2{defaultvoidmethod(){System.out.println("Interface2 method call");}}classMyClassimplementsInterface1,Interface2{publicvoidmethod(){// Invoke the method()from Interface1Interface1.super.method();// Invoke the method() from Interface2Interface2.super.method();}}publicclassMain{publicstaticvoidmain(String[] args){MyClass obj=newMyClass(); obj.method();}}
If a class wants to extend another class and implement multiple interfaces, theextends clause specifying the superclass comes before theimplements 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:
publicinterfaceAnimal{voidsound();}publicclassDogimplementsAnimal{publicvoidsound(){System.out.println("The dog barks.");}}publicclassCatimplementsAnimal{publicvoidsound(){System.out.println("The cat meows.");}}publicclassMain{publicstaticvoidmain(String[] args){Animal dog=newDog();Animal cat=newCat(); 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{voidsound();}publicclassDog implements Animal{publicvoidname(){ System.out.println("JIMMY");}//implementing method sound()publicvoidsound(){ System.out.println("BARK");}}publicclassTest{publicstaticfinalvoidmain(String[] args){ Animal animal=newDog(); 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.
publicstaticfinalvoidmain(String[] args){ Animal animal=newDog(); 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!