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

Java 8的默认方法试图尝试更进一步简化Java API。不幸的是,这一最近的语言扩展带来了一系列复杂的规则,但只有少部分Java开发者意识到这一点。这篇文章告诉你为什么引入默认方法会破坏你的(用户的)代码。

起初看来,默认方法给Java虚拟机的指令集带来了很多新的特性。最终,开发库的人能够在不带来客户端代码的兼容性问题的情况下,升级API。使用

默认方法,任何实现库接口的类都自动适应接口引入的默认方法。一旦用户更新了他实现的类,就能够很简单使用更有意义的方法来覆盖原有默认方法。更好的是,
用户可以在覆盖方法时候,调用接口的默认实现,同时增加业务逻辑。

到现在为止,一切都是很好。但是,在创建接口的时候增加默认方法可能使得Java代码不兼容。这个从下面的例子可以很容易弄明白。我们假设一个库需要它的一个接口的作为输入:


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

Java 8之前,类似于上面联合使用一个接口和一个适配器类的方式,是Java程序语言中一种非常常用的设计模式。该适配器通常由库提供者提供,用于节省库的使用者的某些操作。但是,如果采用接口的方式提供,就类似允许多重继承了。

我们进一步假设一个用户使用了如下的适配器:


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

通过这种实现方式,我们最终可以和库进行交互。注意我们是怎样覆盖bar方法,并为默认的实现增加额外的功能的。

如果将该库移植到Java 8,将会发生什么呢?首先,该库很大可能性会废弃适配器类,而使用默认方法提供该功能。最终,该接口的形式类似如下所示:


  1. interface SimpleInput { 
  2.   void foo(); 
  3.   default void bar() { 
  4.     // some default behavior 
  5.   } 

使用这个新的接口,用户可以更新他的代码,采用默认方法来代替原来的适配器类。通过使用接口代替适配器类的最好的结果是,该类可以继承
(extend)其它的类,而不是特定的适配器。现在我们进行实践,移植MyInput类使其使用默认方法。因为我们现在能继承其它类了,所以我们继承一
个第三方的基础类。我们这里不需要关心这个基础类的作用,我们可以假设这个对我们的功能是有意义的。


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

为了实现原始类相似的功能,我们使用Java 8的新的语法来调用指定接口的默认方法。同时,将我们方法中的一些逻辑移到基础类中去。此时,你可能拍着我的肩膀说,这是一次非常好的重构!

我们相当成功的使用了该库。但是,维护人员需要增加另一个接口来提供更多的功能。该接口被 ComplexInput 接口所代替,这个接口继承自
SimpleInput 接口,并增加了新的方法。因为默认方法通常来说是可以很安全的添加的,因此,维护人员覆盖了 SimpleInput
的默认方法,提供了一个更好的默认方法。毕竟,这对于采用适配器类的方式来说是很平常的事情。


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

新的特性带来了非常好的效果以至于维护 ThirdPartyBaseClass 的人也决定依赖该库。为了完成这项工作,它在 ThirdPartyLibrary 中实现了 ComplexInput 接口。

但是这对 MyInput 类来说意味着什么呢?为了隐式的实现 ComplexInput 接口,可继承
ThirdPartyBaseClass 类,但是调用 SimpleInput
的默认方法突然变成非法的了。结果,用户的代码不能通过编译。现在这种调用是被禁止的,因为Java认为这种在非直接子类中调用父类的父类的方法是非法
的。你只能在 ComplexInput
中去调用该默认方法,但是,这要求你显示的在MyInput中实现该接口。对于库的用户来说,这种改变不是所预期的!

更奇怪的是,Java运行时却不做这种限制。JVM的校验器是允许一个编译好的类去调用 SimpleInput::foo
方法的,即使该类是通过继承更新后的 ThirdPartyBaseClass,从而隐式的实现了ComplexClass。这种限制只存在于编译器中。

我们从这里能学到什么东西呢?简单的说,确保不要在一个接口中覆盖另一个接口的默认方法,既不要用默认方法覆盖,也不要用抽象方法覆盖。总的来说,
请谨慎使用默认方法。即使它使得Java的集合接口API轻易的发生了革命性的变化,但本质上讲,这种继承层级之间的方法调用,增加系统的复杂性。而在
Java 7之前,你只需要沿着线性的类层级去查找真正调用的代码。只有当你觉得非常有必要的时候才去增加这种复杂性。

来源:51CTO

时间: 2024-09-29 18:42:29

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

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

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

Android中在WebView里实现Javascript调用Java类的方法_Android

为了方便网页和Android应用的交互,Android系统提供了WebView中JavaScript网页脚本调用Java类方法的机制.只要调用addJavascriptInterface方法即可映射一个Java对象到JavaScript对象上. 1.映射Java对象到JavaScript对象上 复制代码 代码如下: mWebView = (WebView) findViewById(R.id.wv_content); mWebView.setVerticalScrollbarOverlay(tr

java-关于Java的默认运行方法的机制问题。

问题描述 关于Java的默认运行方法的机制问题. timer.schedule(new MyTask()01000);这一句代码中,new了一个MyTask()的类,怎么就自动调用了里面的run()函数?新手勿喷. 解决方案 timer在到了那个指定时刻,它会反过来调用run,这是约定好的. 解决方案二: 这是类似于回调的机制吗?

采用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

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

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

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

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

JAVA执行javascript方法

之前在一次机缘巧合的情况下,需要时用JAVA执行js方法,查阅了一些文档,找到了相关解决方法,这里和大家分享一下. 在JDK1.6中为我们提供了一个ScriptEngineManager类,ScriptEngineManager 为 ScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态.此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现. ScriptEngineManager 提供了一个方法,

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中this.方法和方法有什么区别?

问题描述 java中this.方法和方法有什么区别? java中this.方法和方法有什么区别? 比如this.setContentView( )和单单写setContentView( )有何区别,谢谢解答 解决方案 this就是代表自身,默认可以不加 解决方案二: Lesson_for_java_day08--类的属性和方法.类的封装性.构造函数和this的使用Java在不同环境下获取当前路径的方法--this.getClass().getResource("")请问:Scanner