向接口引入默认方法是否真的保留了后向兼容性? [英] Does introducing a default method to an interface really preserve back-compatibility?

查看:188
本文介绍了向接口引入默认方法是否真的保留了后向兼容性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



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



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



假设有一个界面:

  package com.example ; 
/ **
*版本1.0
* /
公共接口A {
public void foo();
/ **
*生命,宇宙和一切的答案。
* /
public default int getAnswer(){return 42;}
}

和第二个界面

  package com.acme; 
/ **
*版本1.0
* /
公共接口B {
public void bar();
}

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

  package com.mycompany; 
公共类C实现了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; 
/ **
*版本1.1
* /
公共接口B {
public void bar();
/ **
*生命,宇宙和一切的答案
* @since 1.1
* /
public default int getAnswer(){
返回6 * 9;
}
}

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

 线程中的异常mainjava.lang.IncompatibleClassChangeError:冲突的默认方法:com / example / A.getAnswer com / acme / B.getAnswer 
at com.mycompany.C.getAnswer(C.java)
at com .mycompany.C.main(C.java:12)

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

解决方案

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



<那么,兼容性实际上是关于二进制兼容性。这在 JLS§ 13.5.6中有所解释 - 接口方法声明


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



换句话说,添加默认方法是二进制兼容的更改
,因为它不会引入错误在链接时,即使
在编译时或调用时引入错误。实际上,通过引入默认方法
发生的意外冲突的
风险类似于向
非最终类添加新方法所带来的风险。如果发生冲突,向类
添加方法不太可能触发LinkageError,但是意外覆盖
子进程中的方法可能会导致不可预测的方法行为。两个
更改都可能在编译时导致错误。


你得到 IncompatibleClassChangeError的原因可能是因为,在 B C 类>界面。



另见:




I think I'm slightly confused by the introduction of default methods to interfaces in Java. As I understand it, the idea is that default methods can be introduced to existing interfaces without breaking existing code.

If I implement an interface with a non-abstract class, I (of course) have to define implementations of all the abstract methods in the interface. If the interface defines a default method, I inherit the implementation of that method.

If I implement two interfaces, I obviously have to implement the union of the abstract methods defined in both interfaces. I inherit the implementation of all the default methods; however if there happens to be a collision between default methods in the two interfaces, I must override that method in my implementing class.

This sounds fine, but what about the following scenario?

Suppose there's an interface:

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

and a second interface

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

So I can write the following:

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

So that should be fine, and indeed

java com.mycompany.C 

displays the result 42.

But now suppose acme.com makes the following change to 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;
  }
}

As I understand it, introducing this method is supposed to be safe. But if I now run the existing com.mycompany.C against the new version, I get a runtime error:

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)

That's not entirely surprising, but doesn't it mean that introducing default methods to existing interfaces always runs the risk of breaking existing code? What am I missing?

解决方案

Although adding a default method with the same name in the two interfaces would make the code fail to compile, but once you resolve the compilation error, the binaries obtained after compiling both the interfaces, and the class implementing the interfaces, would be backward compatible.

So, the compatibility is really about binary compatibility. This is being explained in JLS §13.5.6 - Interface Method Declarations:

Adding a default method, or changing a method from abstract to default, does not break compatibility with pre-existing binaries, but may cause an IncompatibleClassChangeError if a pre-existing binary attempts to invoke the method. This error occurs if the qualifying type, T, is a subtype of two interfaces, I and J, where both I and J declare a default method with the same signature and result, and neither I nor J is a subinterface of the other.

In other words, adding a default method is a binary-compatible change because it does not introduce errors at link time, even if it introduces errors at compile time or invocation time. In practice, the risk of accidental clashes occurring by introducing a default method are similar to those associated with adding a new method to a non-final class. In the event of a clash, adding a method to a class is unlikely to trigger a LinkageError, but an accidental override of the method in a child can lead to unpredictable method behavior. Both changes can cause errors at compile time.

The reason you got the IncompatibleClassChangeError is probably because, you didn't recompile your C class, after adding the default method in B interface.

Also see:

这篇关于向接口引入默认方法是否真的保留了后向兼容性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆