Diamond Problem with Default Methods

Naveen AnamaneniUpdated · 5 min read

Default methods brought back a small version of the diamond problem: a class can now inherit code for the same method from a superclass and from several interfaces. The lesson "The Diamond Problem" showed the simplest case, two unrelated defaults, and how X.super.method() resolves it. This lesson covers the complete set of rules the compiler applies, with a running example of each, so you can predict any combination.

The Three Rules

Class first, then the most specific interface, else you choose
Rules3.java▶ Try it
public class Rules3 {
    interface Greeter {
        default String hello() { return "Greeter says hello"; }
    }
 
    static class Base {
        public String hello() { return "Base class says hello"; }
    }
 
    static class Rule1 extends Base implements Greeter { }
 
    interface FormalGreeter extends Greeter {
        @Override
        default String hello() { return "FormalGreeter says good morning"; }
    }
 
    static class Rule2 implements Greeter, FormalGreeter { }
 
    interface QuietGreeter extends Greeter {
        @Override
        String hello();
    }
 
    static class Rule3 implements QuietGreeter {
        @Override
        public String hello() { return "Rule3 must write its own hello"; }
    }
 
    public static void main(String[] args) {
        System.out.println(new Rule1().hello());
        System.out.println(new Rule2().hello());
        System.out.println(new Rule3().hello());
    }
}
Output
Base class says hello
FormalGreeter says good morning
Rule3 must write its own hello

How it works

  1. Rule 1, classes win. Rule1 gets hello() from its superclass Base and from the interface Greeter. A method from the class hierarchy always beats an interface default, so Base's version runs.
  2. Rule 2, the more specific interface wins. FormalGreeter extends Greeter and overrides hello(). Since FormalGreeter is a subtype of Greeter, its default is more specific and wins, even though Rule2 lists both.
  3. An interface can re-abstract a default. QuietGreeter redeclares hello() without a body. Classes implementing QuietGreeter no longer inherit Greeter's default, so Rule3 must write its own.

When the Class Must Choose

Two unrelated interfaces with the same default:

Clash.java
interface Upi { default String pay() { return "UPI"; } }
interface Card { default String pay() { return "Card"; } }
class Wallet implements Upi, Card { }
Compiler output
Clash.java:3: error: types Upi and Card are incompatible;
class Wallet implements Upi, Card { }
^
  class Wallet inherits unrelated defaults for pay() from types Upi and Card
1 error

A default and an abstract method with the same signature, from unrelated interfaces, also don't merge. The abstract one still needs an implementation:

Abs.java
interface Upi { default String pay() { return "UPI"; } }
interface Card { String pay(); }
class Wallet implements Upi, Card { }
Compiler output
Abs.java:3: error: Wallet is not abstract and does not override abstract method pay() in Card
class Wallet implements Upi, Card { }
^
1 error

In both cases the fix is the same: override pay() in Wallet, and call Upi.super.pay() or Card.super.pay() (only for interfaces that have a default) if you want to reuse one.

The Rules as a Decision List

SituationWho wins
A superclass (or the class itself) has the methodThe class version
Several interfaces, one is a subinterface of the otherThe more specific interface
Unrelated interfaces with defaults, or a default plus an abstractNobody: compile error until the class overrides
An interface redeclares the method as abstractIts implementers must implement it

Common Mistakes

Expecting the first interface in the list to win. The order after implements doesn't matter at all.

Forgetting Rule 1. A public method inherited from a superclass silently beats a new interface default.

Calling Card.super.pay() when Card.pay() is abstract. There's no body to call; the compiler rejects it.

Interview Corner

A class implements two interfaces with the same default method. What happens?

If the interfaces are unrelated, compilation fails with "inherits unrelated defaults", and the class must override the method. Inside the override it can call a specific version with InterfaceName.super.method(). If one interface extends the other, the more specific interface's default is used automatically. If the class's superclass already has the method, the class version wins over both.

What are the rules for resolving default method conflicts?

First, a method from the class hierarchy beats any interface default. Second, among interfaces, the most specific subinterface's default wins. Third, if there's still more than one candidate, the class must override the method explicitly.

Practice

  1. Easy: Predict the output: class Base { public String id() { return "base"; } }, interface Tagged { default String id() { return "tag"; } }, class Item extends Base implements Tagged {}, then new Item().id().
  2. Harder: Create Wallet implements Upi, Card from Clash.java and override pay() to return "UPI, then Card" using both X.super.pay() calls.

Quick check: Default method conflicts

4 questions
1A class inherits hello() from its superclass and a default hello() from an interface. Which runs?
2interface B extends A, both with a default go(). class C implements A, B. Which go() runs?
3Two unrelated interfaces both have a default pay(). The class doesn't override it. Result?
4Does the order of interfaces after implements matter?

Key Takeaways

Was this tutorial helpful?

Lesson 137 of 482