Question

The following class prints "M" when run. I was sorta expecting "G".

Can anyone explain this behaviour?

interface G {
    default void print() {
        System.out.println("G");
    }
}
class M {
    public void print() {
        System.out.println("M");
    }
}
class GImpl extends M implements G {}
public class Wierd {
    public static void main(String[] args) {
        G g=new GImpl();
        g.print();
    }
}
Était-ce utile?

La solution

Default methods are used as sort of back-up methods, meaning that they will only be called if there is no concrete implementation of that method.

When looking at your class, we have this encounter order:

  1. GImpl, which calls print().
  2. GImpl has no print(), so going up the tree.
  3. M does have a print(), so using that one.

The only place where I see you using G is as the variable type, which is completely fine, as GImpl is-a G.

If you had wanted to call G's method, then consider the following:

interface G {
    default void print() {
        System.out.println("G");
    }
}
class M {
}
class GImpl extends M implements G {}
public class Weird {
    public static void main(String[] args) {
        G g=new GImpl();
        g.print();
    }
}

Here, we have the following order:

  1. GImpl, which calls print().
  2. GImpl has no print(), so going up.
  3. M does not have print(), going up.
  4. Only have G left, which has a default implementation of print(), so gets called. Note: If this was absent, your code would not even compile.

So with default methods you cannot override already existing behaviour. You can however add behaviour when no other behaviour was in place yet.

Autres conseils

When resolving a virtual method such as print(), the JVM searches the superclass chain for implementations as well as the transitive closure of the interfaces for default implementations. If an implementation is found in an ancestor class, it will always take precedence over a default implementation in an interface.

The reason for this rule is for compatibility.

Let's start off with a slightly modified example:

interface G { }

class M {
    void print() { ... }
}

class GImpl extends M implements G { }

...

new GImpl().print();

Clearly, M's print method will be called.

Now consider that G is evolved by adding a default implementation of print:

interface G {
    default void print() { ... }
}

Recall that the primary purpose of default methods is to facilitate interface evolution. Part of this is the ability to add new methods to an interface. Another part of this is not breaking existing code. If the addition of a default method to G were to change the behavior of existing classes such as GImpl, this would most likely cause programs to break. In addition I think that most people would be surprised by this.

Thus, the rule was established that implementations in the superclass chain always take precedence over default implementations in the interfaces, so that programs won't change behavior unexpectedly if a default method is added to an interface.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top