Interface Inheritance in Java
Interface Inheritance
Inheritance allows you to inherit another interface's methods and constants and create a hierarchy that can be used with polymorphism. Just like a class can inherit another class, an interface can inherit another interface. This is done using the extends keyword when declaring an interface.
public interface MyInterface1 {
void method1();
void method2();
}
public interface MyInterface2 extends MyInterface1 {
void method3();
}
In this example, MyInterface2 inherits MyInterface1. The methods on MyInterface2 would be method1(), method2(), and method3().
Multiple Inheritance
Unlike classes, interfaces support multiple inheritance. An interface can inherit zero or more interfaces.
public interface Identifiable {
UUID id();
}
public interface JsonAware {
String toJson();
JsonAware fromJson(final String json);
}
public interface Message extends Identifiable, JsonAware {
}
This defines the Identifiable and JsonAware interfaces. It then declares the Message interface that inherits both Identifiable and JsonAware.
Overriding Methods
If you have a default method in an interface, you can override that method just like you could override a method in a class.
public interface MyInterface1 {
default void method() {
// ...
}
}
public interface MyInterface2 extends MyInterface1 {
@Override
default void method() {
// ...
}
}
In MyInterface2, it overrides method() and provides a different implementation.
If you need to call the method in the interface that it is extending, you can do so with the following:
public interface MyInterface2 extends MyInterface1 {
@Override
default void method() {
MyInterface1.super.method();
// ...
}
}
Another reason to override a method is to change the return type of the method. Method overriding supports covariant return types. An interface can declare a method with a return type. Another interface can extend that interface and override that method and change the return type to a subclass of that type. This cannot be done if the method returns void or a primitive type. It can be done when it returns an object.
public interface Identifiable {
Object id();
}
public interface Message extends Identifiable {
@Override
UUID id();
}
Preventing Inheritance
Java 17 introduces the ability to control what interfaces can inherit an interface through sealed interfaces. Using sealed interfaces will not only prevent inheritance with interfaces but will also prevent what classes can implement that interface.
Sealed interfaces are marked with the sealed keyword and have a permits clause. The permits clause defines what interfaces can inherit the interface.
public sealed interface Identifiable
permits Message, User {
}
public non-sealed interface Message {
}
public non-sealed interface User {
}
The Identifiable interface is marked as sealed and only permits Message and User to inherit it. The Message and User interfaces inherit Identifiable and are marked as non-sealed so they can be extended by other interfaces or implemented by other classes.
Static Method and Constant Hiding
Hiding can occur when you have the same static method in an interface that extends another interface.
public interface MyInterface1 {
static void greet() {
System.out.println("Hello MyInterface1");
}
}
public interface MyInterface2 extends MyInterface1 {
static void greet() {
System.out.println("Hello MyInterface2");
}
}
If you were to call greeting inside MyInterface2, it would print "Hello MyInterface2" and hide the same static method declared in MyInterface1. To get around this, you can specify the interface of the static method you want called.
// Prints: Hello MyInterface1
MyInterface1.greet();
// Prints: Hello MyInterface2
MyInterface2.greet();
This can also occur with constants on interfaces.
Conclusion
Interface support not only inheritance but multiple inheritance. You can override default methods to provide a different implementation or change the return type of a method. Be careful of static methods and constant hiding when using inheritance with interfaces.