Java's Approach to Multiple Inheritance
The Core Rule
Java disallows multiple class inheritance — the extends keyword takes exactly one class name. This eliminates the diamond problem for state entirely.
Java does allow:
- Implementing multiple interfaces (unlimited multiple inheritance of type)
- Since Java 8, inheriting default methods from multiple interfaces (multiple inheritance of behaviour, with explicit conflict resolution rules)
Interface Default Method Inheritance
Since Java 8, interfaces can provide default method implementations. A class that implements two interfaces with conflicting default methods for the same signature must deal with the ambiguity.
Rule 1: Class Wins Over Interface
If a class (or its superclass) defines a concrete method, it always wins over any default method from an interface, regardless of how many interfaces provide defaults:
interface A {
default void m() { System.out.println("A.m"); }
}
interface B {
default void m() { System.out.println("B.m"); }
}
class Parent {
public void m() { System.out.println("Parent.m"); }
}
class Child extends Parent implements A, B {
// inherits Parent.m() — no conflict, class wins
}
Child c = new Child();
c.m(); // "Parent.m"
The class implementation (whether declared in the class itself or inherited from a superclass) always takes precedence. This rule preserves the invariant that adding a default method to an existing interface cannot silently change the behaviour of existing classes.
Rule 2: More Specific Interface Wins
If two interfaces provide defaults and one extends the other, the more specific interface’s default wins:
interface A {
default void m() { System.out.println("A.m"); }
}
interface B extends A {
@Override
default void m() { System.out.println("B.m"); }
}
class C implements B, A { // B is more specific than A
// inherits B.m()
}
C c = new C();
c.m(); // "B.m"
B is more specific than A because B extends A. The more specific override takes precedence.
Rule 3: Unresolvable Conflict → Compile Error
If two unrelated interfaces provide conflicting defaults and neither is more specific than the other, the implementing class must explicitly override the method — otherwise it is a compile error:
interface A {
default void m() { System.out.println("A.m"); }
}
interface B {
default void m() { System.out.println("B.m"); }
}
class C implements A, B {
@Override
public void m() { // MUST override
A.super.m(); // can call one explicitly
B.super.m(); // or both
System.out.println("C.m");
}
}
The syntax InterfaceName.super.method() is the mechanism for calling a specific interface’s default implementation. This is distinct from super.method() (which calls the superclass version) and is only available within a class that implements the interface.
Comprehensive Conflict Example
interface Printer {
default String format() { return "text"; }
}
interface Logger {
default String format() { return "log"; }
}
interface FancyPrinter extends Printer {
@Override
default String format() { return "fancy: " + Printer.super.format(); }
}
class Console implements FancyPrinter, Logger {
@Override
public String format() {
return "[" + FancyPrinter.super.format() + "][" + Logger.super.format() + "]";
}
}
// Console().format() → "[fancy: text][log]"
Here FancyPrinter wins over Printer (more specific), but FancyPrinter and Logger are unrelated, so Console must override.
Abstract Class + Interface Conflict
An abstract class can partially resolve conflicts, leaving some for concrete subclasses:
interface A {
default void m() { System.out.println("A.m"); }
}
interface B {
default void m() { System.out.println("B.m"); }
}
abstract class AbstractC implements A, B {
// must either: resolve m(), or redeclare it abstract
@Override
public abstract void m(); // push the resolution to concrete subclasses
}
class ConcreteC extends AbstractC {
@Override
public void m() { System.out.println("ConcreteC.m"); }
}
Declaring the conflicting method as abstract in the abstract class defers the conflict to the concrete subclass.
Why No Multiple State Inheritance?
Java’s designers made a conscious decision to exclude multiple class inheritance for state. The rationale:
-
Simplicity: single inheritance is easy to understand, implement, and optimise. C++‘s experience with virtual inheritance showed that the feature introduces significant complexity (virtual base pointers, changes to object layout, constructor forwarding rules).
-
Few genuine use cases: observed practice showed that most legitimate uses of multiple inheritance could be expressed through single inheritance plus interfaces, or through composition (holding a reference to another object as a field).
-
The diamond problem for state has no clean resolution: should a
TeachingAssistantthat inherits from bothStudentandEmployee(which both inherit fromPerson) have one copy ofPerson’s name and ID, or two? If one, which path’s constructor initialises it? If two, which one is the “real” one? The ambiguity is semantic, not just syntactic. -
Interfaces solve the type problem: the main practical need for multiple inheritance is for a class to satisfy multiple type contracts — and interfaces handle this perfectly.
Default Methods: Pragmatic Compromise
Default methods were added in Java 8 primarily to enable API evolution. Consider java.util.Collection, which is implemented by thousands of third-party classes. Before Java 8, adding a new abstract method to Collection would break every existing implementation. With default methods, a new method stream() can be added to Collection with a sensible default implementation, and all existing code continues to compile and work.
The clash-resolution rules (class wins; more specific interface wins; otherwise override) were carefully designed so that adding a default method to an existing interface never silently changes the behaviour of existing code — the worst case is a compile error in an implementing class that already had ambiguity.