What is the diamond problem and how does Java resolve it… — Cracked Java
// Object-Oriented Programming · Abstract Classes vs Interfaces (Post Java 8)
SeniorTheoryBig TechGoogle

What is the diamond problem and how does Java resolve it for default methods?

The diamond problem is the ambiguity that arises when a type inherits the same method through two different paths. Java's resolution rule is: class wins over interface, more specific interface wins over less specific, and otherwise the implementer must override and disambiguate explicitly with Interface.super.method(). Without those rules, default methods would have re-opened C++'s classic multiple-inheritance mess.

The classic shape

interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }

class C implements A, B {
    // compile error: types A and B contain conflicting default methods
}

Java forces C to break the tie — silence would mean picking one arbitrarily, which would be a footgun.

Resolution rule 1: class wins

class Parent { public String hello() { return "Parent"; } }
interface I  { default String hello() { return "I"; } }

class Child extends Parent implements I {
    // no error; Parent.hello wins
}

new Child().hello();      // "Parent"

If an inherited concrete method (from a class) and a default method (from an interface) collide, the class wins. The rationale: classes have been the source of state and behavior since 1996; introducing default methods in Java 8 must not silently override concrete superclass methods.

Resolution rule 2: more specific interface wins

interface A         { default String hello() { return "A"; } }
interface B extends A { default String hello() { return "B"; } }

class C implements A, B { }    // no conflict

new C().hello();         // "B" — the more specific interface wins

Because B extends A, B's hello() is a refinement of A's. No ambiguity.

Resolution rule 3: explicit disambiguation

When the rules don't pick a winner, the implementer must:

interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }

class C implements A, B {
    @Override public String hello() {
        return A.super.hello() + " + " + B.super.hello();
    }
}

new C().hello();          // "A + B"

The InterfaceName.super.method() syntax explicitly names which inherited default to invoke. You can call one, the other, both, or neither — your choice.

A diamond with three interfaces

interface Greeter           { default String greet() { return "hi"; } }
interface FormalGreeter     extends Greeter { default String greet() { return "good day"; } }
interface CasualGreeter     extends Greeter { default String greet() { return "yo"; } }

class Person implements FormalGreeter, CasualGreeter {
    @Override public String greet() {
        return FormalGreeter.super.greet();
    }
}

FormalGreeter and CasualGreeter both extend Greeter, but neither is more specific than the other — the compiler can't pick. Person must override and pick a side.

Why Java handled it this way

C++ has virtual inheritance and a single-instance rule for diamonds; that approach interacts poorly with state. Java avoided state on interfaces precisely to keep the diamond manageable: the only thing to resolve is behavior, and the rules above give a deterministic answer.

Mark your status