谨慎使用Java8的默认方法_java

默认方法给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的默认方法进行的详细解释,希望对大家的学习有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索java8
默认方法
java 方法参数默认值、java8 接口默认方法、java默认构造方法、java方法默认修饰符、java方法默认值,以便于您获取更多的相关知识。

时间: 2024-10-12 04:24:43

谨慎使用Java8的默认方法_java的相关文章

Java8新特性之默认方法(default)浅析_java

一.什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法.只需在方法名前面加个default关键字即可. 为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现.然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现.所以引进的默认方法

java8 default methods 默认方法的概念与代码解析

一.基本概念         Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.       默认方法使您能够添加新的功能到你现有库的接口中,并确保与采用老版本接口编写的代码的二进制兼容性.   

Java 8默认方法会破坏你的(用户的)代码

Java 8的默认方法试图尝试更进一步简化Java API.不幸的是,这一最近的语言扩展带来了一系列复杂的规则,但只有少部分Java开发者意识到这一点.这篇文章告诉你为什么引入默认方法会破坏你的(用户的)代码. 起初看来,默认方法给Java虚拟机的指令集带来了很多新的特性.最终,开发库的人能够在不带来客户端代码的兼容性问题的情况下,升级API.使用 默认方法,任何实现库接口的类都自动适应接口引入的默认方法.一旦用户更新了他实现的类,就能够很简单使用更有意义的方法来覆盖原有默认方法.更好的是, 用

函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava2](这到底是什么)第三部分

本文讲的是函数式接口.默认方法.纯函数.函数的副作用.高阶函数.可变的和不可变的.函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava2](这到底是什么)第三部分, 太棒了,我们又来到新的一天.这一次,我们要学一些新的东西让今天变得有意思起来. 大家好,希望你们都过得不错.这是我们的 RxJava2 Android 系列的第三篇文章. 第一部分 第二部分 在这篇文章中,我们将讨论函数式的接口,函数式编程,Lambda 表达式以及与 Java 8 的相关的其它内容.这

Java 8 默认方法和多继承深入解析

以前经常谈论的Java对比c++的一个优势是Java中没有多继承的问题. 因为Java中子类只能继承(extends)单个父类, 尽管可以实现(implements)多个接口,但是接口中只有抽象方法,方法体是空的,没有具体的方法实现,不会有方法冲突的问题. 这些都是久远的说法了,自从今年Java 8发布后, 接口中也可以定义方法了(default method). 之所以打破以前的设计在接口中 增加具体的方法, 是为了既有的成千上万的Java类库的类增加新的功能, 且不必对这些类重新进行设计.

采用Java 8中Lambda表达式和默认方法的模板方法模式

原文链接 作者:   Mohamed Sanaulla  译者: 李璟(jlee381344197@gmail.com) 模板方法模式是"四人帮"(译者注:Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides)所著<Design Patterns book>一书中所描述的23种设计模式其中的一种,该模式旨在: "Define the skeleton of an algorithm in an op

Java8的default方法详细介绍_java

什么是default方法? Java 8发布以后,可以给接口添加新方法,但是,接口仍然可以和它的实现类保持兼容.这非常重要,因为你开发的类库可能正在被多个开发者广泛的使用着.而Java 8之前,在类库中发布了一个接口以后,如果在接口中添加一个新方法,那些实现了这个接口的应用使用新版本的接口就会有崩溃的危险. 有了Java 8,是不是就没有这种危险了?答案是否定的. 给接口添加default方法可能会让某些实现类不可用. 首先,让我们看下default方法的细节. 在Java 8中,接口中的方法可

Java8中使用流方式查询数据库的方法_java

由于关系型数据库操作语言和面向对象语言之间的差异,如今我们仍然需要花费许多时间建立数据库与 Java 应用之间互相沟通的桥梁.通常,我们可以编写自己的映射层(mapping layer),或者使用第三方的 ORM(Object Relational Mapper)对象关系映射框架,比如 Hibernate.ORM 框架虽然使用起来很方便,但是如何正确地配置和提高框架操作数据库的性能却不太容易,ORM 框架往往会使我们的应用性能下降. 最近,我贡献了一个新的开源项目--Speedment,它能使我

Java获取代码中方法参数名信息的方法_java

前言 大家都知道随着java8的使用,在相应的方法签名中增加了新的对象Parameter,用于表示特定的参数信息,通过它的getName可以获取相应的参数名.即像在代码中编写的,如命名为username,那么在前台进行传参时,即不需要再编写如@Parameter("username")类的注解,而直接就能进行按名映射. 如下的代码参考所示: public class T { private interface T2 { void method(String username, Stri