谨慎使用Java8的默认方法

 更新时间:2016年01月23日 10:13:34   投稿:lijiao  
为什么要谨慎使用Java8的默认方法?本文给出了为什么要慎用Java8默认方法的原因,解释的很详细,感兴趣的朋友可以参考一下

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

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

interface SimpleInput {
 void foo();
 void bar();
}
 
abstract class SimpleInputAdapter implements SimpleInput {
 @Override
 public void bar() {
 // some default behavior ...
 }
}
 

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

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

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类改造成使用默认方法。由于现在我们可以继承别的类了,我们再额外地扩展一个第三方的基类试试。这个基类具体是做什么的在这里并不重要,我们先假设一下这么做对我们这个用例来说是有意义的。

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类的默认方法并添加了一些额外的动作来给用户提供一个更好的默认实现。毕竟使用适配器类的时候这个做法也十分常见:

interface ComplexInput extends SimpleInput {
 void qux();
 @Override
 default void bar() {
 SimpleInput.super.bar(); 
 // so complex, we need to do more ...
 }
} 
 

这个新特性看起来非常不错,因此ThirdPartyBaseClass类的维护人员也决定使用这个库了。为了实现这个,他将ThirdPartyBaseClass类实现了ComplexInput接口。

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

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

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以前,你只需遍历线性的类层次结构看一下实际调用的代码就可以了。当你觉得的确需要的时候,再去使用默认方法。

以上就是针对为什么要慎用Java8的默认方法进行的详细解释,希望对大家的学习有所帮助。

相关文章

  • Java中如何判断线程池任务已执行完成

    Java中如何判断线程池任务已执行完成

    这篇文章主要介绍了Java中如何判断线程池任务已执行完成,文章围绕主题展开详细相关资料,具有一定的参考价值需要的小伙伴可以参考以参考一下
    2022-05-05
  • Java使用iTextPDF生成PDF文件的实现方法

    Java使用iTextPDF生成PDF文件的实现方法

    这篇文章主要介绍了Java使用iTextPDF生成PDF文件的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 记一次springboot服务凌晨无故宕机问题的解决

    记一次springboot服务凌晨无故宕机问题的解决

    这篇文章主要介绍了记一次springboot服务凌晨无故宕机问题的解决,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot项目网页加载出现Whitelabel Error Page的解决

    SpringBoot项目网页加载出现Whitelabel Error Page的解决

    这篇文章主要介绍了SpringBoot项目网页加载出现Whitelabel Error Page的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java调用dll文件的实现解析

    Java调用dll文件的实现解析

    这篇文章主要介绍了Java调用dll文件的实现解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Spring Boot 启动、停止、重启、状态脚本

    Spring Boot 启动、停止、重启、状态脚本

    今天给大家分享Spring Boot 项目脚本(启动、停止、重启、状态),通过示例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-06-06
  • Spring中bean的初始化和销毁几种实现方式详解

    Spring中bean的初始化和销毁几种实现方式详解

    这篇文章主要介绍了Spring中bean的初始化和销毁几种实现方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • java8 stream排序以及自定义比较器方式

    java8 stream排序以及自定义比较器方式

    这篇文章主要介绍了java8 stream排序以及自定义比较器方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java 判断字符串a和b是否互为旋转词

    Java 判断字符串a和b是否互为旋转词

    本篇文章主要介绍了判断字符串a和b是否互为旋转词的相关知识,具有很好的参考价值。下面跟着小编一起来看下吧
    2017-05-05
  • Idea创建多模块maven聚合项目的实现

    Idea创建多模块maven聚合项目的实现

    这篇文章主要介绍了Idea创建多模块maven聚合项目的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12

最新评论