向接口引入默认方法是否真的保留了后向兼容性?

时间:2014-03-24 19:11:46

标签: java-8

我认为我对Java中的接口引入默认方法感到有些困惑。据我了解,其想法是可以在不破坏现有代码的情况下将默认方法引入现有接口。

如果我使用非抽象类实现接口,我(当然)必须定义接口中所有抽象方法的实现。如果接口定义了默认方法,我将继承该方法的实现。

如果我实现两个接口,我显然必须实现两个接口中定义的抽象方法的并集。我继承了所有默认方法的实现;但是如果两个接口中的默认方法之间发生冲突,我必须在我的实现类中重写该方法。

这听起来不错,但是下面的场景呢?

假设有一个界面:

package com.example ;
/** 
* Version 1.0
*/
public interface A {
  public void foo() ;
  /**
  * The answer to life, the universe, and everything.
  */
  public default int getAnswer() { return 42 ;}
}

和第二个界面

package com.acme ;
/** 
* Version 1.0
*/
public interface B {
  public void bar() ;
}

所以我可以写下以下内容:

package com.mycompany ;
public class C implements com.example.A, com.acme.B {
  @Override
  public void foo() {
    System.out.println("foo");
  }
  @Override
  public void bar() {
    System.out.println("bar");
  }
  public static void main(String[] args) {
    System.out.println(new C().getAnswer());
  }
}

所以这应该没问题,而且确实

java com.mycompany.C 

显示结果42。

但现在假设acme.com对B进行了以下更改:

package com.acme ;
/** 
* Version 1.1
*/
public interface B {
  public void bar() ;
  /**
  * The answer to life, the universe, and everything
  * @since 1.1
  */
  public default int getAnswer() {
    return 6*9;
  }
}

据我所知,介绍这种方法应该是安全的。但是如果我现在针对新版本运行现有的com.mycompany.C,我会收到运行时错误:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: com/example/A.getAnswer com/acme/B.getAnswer
at com.mycompany.C.getAnswer(C.java)
at com.mycompany.C.main(C.java:12)

这并不奇怪,但这并不意味着将默认方法引入现有接口总是存在破坏现有代码的风险吗?我错过了什么?

2 个答案:

答案 0 :(得分:14)

虽然在两个接口中添加一个具有相同名称的默认方法会使代码无法编译,但是一旦解决了编译错误,在编译两个接口后获得二进制文件,实现接口的类,将向后兼容。

因此,兼容性实际上是关于二进制兼容性。这在JLS §13.5.6 - Interface Method Declarations

中有所解释
  

添加默认方法,或将方法从abstract更改为   默认情况下,不会破坏与预先存在的二进制文件的兼容性,但是   如果预先存在的二进制文件可能会导致IncompatibleClassChangeError   尝试调用该方法。如果符合条件,则会发生此错误   type,T,是两个接口的子类型,I和J,其中I和J都是   声明具有相同签名和结果的默认方法,和   我和J都不是另一个的子接口。

     

换句话说,添加默认方法是二进制兼容的更改   因为它不会在链接时引入错误,即使它也是如此   在编译时或调用时引入错误。在实践中,   通过引入默认方法发生意外冲突的风险   类似于向a添加新方法的那些   非决赛课。如果发生冲突,请向类添加方法   不太可能触发LinkageError,而是意外覆盖   孩子中的方法可能导致不可预测的方法行为。都   更改可能会在编译时导致错误。

您获得IncompatibleClassChangeError的原因可能是因为在C界面中添加默认方法后,您没有重新编译B类。

另见:

答案 1 :(得分:2)

即使您通过明确选择将冲突的默认方法调用委托给哪个接口来更新您的实现,其中一个接口的细微更改仍然会使您的代码无法编译。

E.g。你可以像这样修复一个类T

interface I {
    default void m() {}
}

interface J {
    default void m() {}
}

class T implements I, J {
    @Override
    public void m() { // forced override
        I.super.m(); // OK
    }
}

一切都会好的,直到这样的改变:

interface J extends I {
    @Override
    default void m() {}
}

如果只重新编译接口J,方法T::m仍会委托给I::m。但是T本身的编译将不再成功 - 它会因error: bad type qualifier I in default super call而失败:

class T implements I, J { // redundant I, but not an error
    @Override
    public void m() { // override not necessary, T::m resolves to J::m
        I.super.m(); // ERROR
    }
}