Java 8中接口中使用默认方法的意外行为

时间:2014-03-18 05:30:55

标签: interface default java-8

以下类在运行时打印“M”。我有点期待“G”。

任何人都可以解释这种行为吗?

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();
    }
}

2 个答案:

答案 0 :(得分:5)

默认方法用作一种备份方法,这意味着只有在没有该方法的具体实现时才会调用它们。

在查看你的课程时,我们有这样的遭遇顺序:

  1. GImpl,调用print()
  2. GImpl没有print(),所以上了树。
  3. M确实有一个print(),所以使用那个。
  4. 我使用G看到您的唯一地方是变量类型,完全没问题,因为GImpl 是-a G。< / p>

    如果您想调用G方法,请考虑以下事项:

    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();
        }
    }
    

    在这里,我们有以下顺序:

    1. GImpl,调用print()
    2. GImpl没有print(),所以上去。
    3. M没有print(),正在上升。
    4. 只剩下G,其默认实现为print(),因此会被调用。注意:如果没有,您的代码甚至都不会编译。
    5. 因此,使用默认方法,您无法覆盖已存在的行为。但是,当没有其他行为到位时,您可以添加行为。

答案 1 :(得分:0)

在解析诸如print()的虚拟方法时,JVM会在超类链中搜索实现以及默认实现的接口的传递闭包。如果在祖先类中找到实现,它将始终优先于接口中的默认实现。

此规则的原因是兼容性。

让我们从略微修改的例子开始:

interface G { }

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

class GImpl extends M implements G { }

...

new GImpl().print();

显然,将调用M的print方法。

现在考虑通过添加G的默认实现来演变print

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

回想一下,默认方法的主要目的是促进界面演变。部分原因是能够向界面添加新方法。另一部分不是破坏现有代码。如果向G添加默认方法是为了更改现有类(如GImpl)的行为,则很可能会导致程序中断。另外我认为大多数人会对此感到惊讶。

因此,规则确定超类链中的实现始终优先于接口中的默认实现,因此如果将默认方法添加到接口,程序将不会意外地更改行为。