Diamond Problem with Default Methods
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
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());
}
}Base class says hello
FormalGreeter says good morning
Rule3 must write its own helloHow it works
- Rule 1, classes win.
Rule1getshello()from its superclassBaseand from the interfaceGreeter. A method from the class hierarchy always beats an interface default, soBase's version runs. - Rule 2, the more specific interface wins.
FormalGreeter extends Greeterand overrideshello(). SinceFormalGreeteris a subtype ofGreeter, its default is more specific and wins, even thoughRule2lists both. - An interface can re-abstract a default.
QuietGreeterredeclareshello()without a body. Classes implementingQuietGreeterno longer inheritGreeter's default, soRule3must write its own.
When the Class Must Choose
Two unrelated interfaces with the same default:
interface Upi { default String pay() { return "UPI"; } }
interface Card { default String pay() { return "Card"; } }
class Wallet implements Upi, Card { }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 errorA default and an abstract method with the same signature, from unrelated interfaces, also don't merge. The abstract one still needs an implementation:
interface Upi { default String pay() { return "UPI"; } }
interface Card { String pay(); }
class Wallet implements Upi, Card { }Abs.java:3: error: Wallet is not abstract and does not override abstract method pay() in Card
class Wallet implements Upi, Card { }
^
1 errorIn 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
| Situation | Who wins |
|---|---|
| A superclass (or the class itself) has the method | The class version |
| Several interfaces, one is a subinterface of the other | The more specific interface |
| Unrelated interfaces with defaults, or a default plus an abstract | Nobody: compile error until the class overrides |
| An interface redeclares the method as abstract | Its 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
- Easy: Predict the output:
class Base { public String id() { return "base"; } },interface Tagged { default String id() { return "tag"; } },class Item extends Base implements Tagged {}, thennew Item().id(). - Harder: Create
Wallet implements Upi, CardfromClash.javaand overridepay()to return"UPI, then Card"using bothX.super.pay()calls.
Key Takeaways
Was this tutorial helpful?