Java 8默认方法致代码不兼容,浅析

默认方法给JVM的指令集增长了一个很是不错的新特性。使用了默认方法以后,若是库中的接口增长了新的方法,实现了这个接口的用户类可以自动得到这个方法的默认实现。一旦用户想更新他的实现类的话,只需覆盖一下这个默认方法就能够了,取而代之的是一个在特定场景下更有意义的实现。更棒的是,用户能够在重写的方法里面调用接口的默认实现来增长一些额外的功能。html

目前为止一切都还不错。然而,给现有的Java接口增长默认方法可能会致使代码的不兼容。看个例子就很容易能明白了。假设有一个库,它须要用户实现它的一个接口做为输入:java

1
2
3
4
5
6
7
8
9
10
11
interface SimpleInput {
   void foo();
   void bar();
}
  
abstract class SimpleInputAdapter  implements SimpleInput {
   @Override
   public void bar() {
     // some default behavior ...
   }
}

在Java 8之前,上述这种接口和一个对应的适配器类的组合在Java语言中是一种很常见的模式。类库的开发人员提供了一个适配器来减小库使用者的编码量。然而提供这个接口的目的实际上是为了能实现某种相似多重继承的关系。web

咱们假设有一个用户使用了这个适配器:ide

1
2
3
4
5
6
7
8
9
10
11
class MyInput  extends SimpleInputAdapter{
   @Override
   public void foo() {
     // do something ...
   }
   @Override
   public void bar() {
     super .bar();
     // do something additionally ...
   }
}

有了这个实现,用户能够和库进行交互了。注意这个实现是如何重写bar方法来给默认的实现增长额外的功能的。工具

那若是这个库迁移到Java 8的话会怎样?首先,这个库极可能会废弃掉这个适配器类并将这个功能迁移到默认方法里。最终这个接口看起来会是这样的:开发工具

interface SimpleInput {
  void foo();
  default void bar() {
    // some default behavior
  }}

有了这个新接口后,用户得更新他的代码来使用这个默认方法,而再也不是适配器类了。使用新接口而非适配器类的一大好处就是,用户能够去继承一个别的类而不是这个适配器类了。咱们来动手实践一下,将MyInput类改形成使用默认方法。因为如今咱们能够继承别的类了,咱们再额外地扩展一个第三方的基类试试。这个基类具体是作什么的在这里并不重要,咱们先假设一下这么作对咱们这个用例来讲是有意义的。测试

1
2
3
4
5
6
7
8
9
10
11
class MyInput  extends ThirdPartyBaseClass  implements SimpleInput {
   @Override
   public void foo() {
     // do something ...
   }
   @Override
   public void bar() {
     SimpleInput. super .foo();
     // do something additionally ...
   }
}

为了实现和原先那个类一样的功能,这里咱们用到了Java 8的新语法来调用接口的默认方法。一样的,咱们把myMethod的逻辑放到某个基类MyBase里面。能够捶捶肩膀放松下了。重构以后棒极了!编码

咱们使用的这个库获得了很大的改进。然而,维护人员须要添加另外一个接口来实现一些额外的功能。这个接口叫作CompexInput ,它继承了SimpleInput类,并增长了一个额外的方法。因为一般都认为默认方法是能够放心地添加的,所以维护人员重写了SimpleInput类的默认方法并添加了一些额外的动做来给用户提供一个更好的默认实现。毕竟使用适配器类的时候这个作法也十分常见:spa

1
2
3
4
5
6
7
8
interface ComplexInput  extends SimpleInput {
   void qux();
   @Override
   default void bar() {
     SimpleInput. super .bar();
     // so complex, we need to do more ...
   }
}

这个新特性看起来很是不错,所以ThirdPartyBaseClass类的维护人员也决定使用这个库了。为了实现这个,他将ThirdPartyBaseClass类实现了ComplexInput接口。code

但这样的话对MyInput类意味着什么?因为它继承了ThirdPartyBaseClass类,所以默认实现了ComplexInput接口,这样的话调用SimpleInput的默认方法就不合法了。结果就是,用户的代码最后没法经过编译。还有就是,如今已经完全没法调用这个方法了,由于Java把这种调用间接父类的super-super方法认为是不合法的。你只能去调用ComplexInput接口的默认方法了。然而这首先须要你在MyInput类中显式的实现一下这个接口。对于这个库的用户而言,这些改动彻底是意想不到的。

(注:简单点说其实就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface A {
     default void  test() {
         
     }
}
 
interface B  extends A {
     default void test() {
         
     }
}
 
public class Test  implements B {
     public void test() {
         B. super .test();
         //A.super.test();  错误
     }
}

固然这么写的话是用户主动选择实现了B接口,而文中的例子因为引入了一个基类,所以因为库和基类中都进行了一个看似没有影响的改动,实际上却致使用户代码没法经过编译)

很奇怪的是,Java在运行时并无对这个进行区分。JVM的校验器容许一个编译过的类进行SimpleInput::foo方法的调用,尽管加载的这个类继承了ThirdPartyBaseClass的更新版本后隐式地实现了ComplexInput接口。要怪只能怪编译器了。(注:编译器与运行时的行为不一致)

那咱们从中学到了什么?简单地说,不要在另外一个接口中重写原接口的默认方法。不要用另外一个默认方法来重写它,也不要某个抽象方法来重写它。总而言之,使用默认方法时应当十分谨慎。虽然它们使得Java现有的集合库的接口更容易改进了,但它容许你在类的继承结构中进行方法调用,这本质上实际上是增长了复杂性。在Java 7之前,你只需遍历线性的类层次结构看一下实际调用的代码就能够了。当你以为的确须要的时候,再去使用默认方法。

舒适小提示:好的资源,在 Java开发中能事半功倍!

原文转载至:http://it.deepinmind.com/java/2014/05/19/java-8-default-methods-can-break-your-code.html

业界被公认为最好的Java开发平台:IntelliJ IDEA 

最实惠、综合全面的J2EE IDE与Web开发工具套件:MyEclipse

多平台Java安装文件生成工具:install4j

全面测试Java程序的工具:Parasoft Jtest

相关文章
相关标签/搜索