文档章节

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

kouxunli1
 kouxunli1
发布于 2014/05/29 14:00
字数 1717
阅读 117
收藏 5

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

目前为止一切都还不错。然而,给现有的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语言中是一种很常见的模式。类库的开发人员提供了一个适配器来减少库使用者的编码量。然而提供这个接口的目的其实是为了能实现某种类似多重继承的关系。

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

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

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接口。

但这样的话对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


本文转载自:http://www.evget.com/article/2014/5/29/21076.html

共有 人打赏支持
kouxunli1
粉丝 24
博文 144
码字总数 59616
作品 0
九龙坡
架构师
java.io.Serializable浅析

 Java API中java.io.Serializable接口源码: 1 public interface Serializable {2 }   类通过实现java.io.Serializable接口可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化...

偶尔诗文
2015/08/16
0
0
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 thos......

cloud-coder
2014/04/02
0
0
Java读带有BOM的UTF-8文件乱码原因及解决方法

最近在处理文件时发现了同样类型的文件使用的编码可能是不同的。所以想将文件的格式统一一下(因为UTF-8的通用性,决定往UTF-8统一),遇见的第一个问题是:如何查看现有文件的编码方式。 上网...

张志浩
2012/11/06
0
0
Java 8新特性探究(二)深入解析默认方法

上篇讲了 lambda表达式的语法,但只是 JEP126 特性的一部分,另一部分就是默认方法(也称为虚拟扩展方法或防护方法) 什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,...

OSC闲人
2013/11/13
0
49
new String(str.getBytes("iso8859-1"), "UTF-8");

1.编码基础知识 最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。 1.1. iso8859-1 通常叫做Latin-1 属于单字节编码,最...

八戒_o
2015/11/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

awk命令用法介绍

10月18日任务 9.6/9.7 awk 1.awk(上)(下) 1.awk 分段操作功能 指定分隔符,并把第一段打印出来,不会改动文件内容 将所有内容打印出来 awk 没有指定分隔符号,则会默认用空格或者空白字符...

hhpuppy
33分钟前
0
0
Spring Cloud Eureka Server高可用之:在线扩容

本文共 1591字,阅读大约需要 6分钟 ! 概述 业务微服务化以后,我们要求服务高可用,于是我们可以部署多个相同的服务实例,并引入负载均衡机制。而微服务注册中心作为微服务化系统的重要单元...

CodeSheep
45分钟前
1
0
内网esxi主机上安装CoreOS虚拟机

CoreOS是一个为专门运行容器而设计的轻量级linux发行版,旨在通过轻量的系统架构和灵活的应用程序部署能力简化数据中心的维护成本和复杂度。它没有包管理工具,运行容器化应用以提供服务;默...

hiwill
今天
1
0
20181018 上课截图

![](https://oscimg.oschina.net/oscnet/49f66c08ab8c59a21a3b98889d961672f30.jpg) ![](https://oscimg.oschina.net/oscnet/a61bc2d618b403650dbd4bf68a671fabecb.jpg)......

小丑鱼00
今天
3
0
WinDbg

参考来自:http://www.cnit.net.cn/?id=225 SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols ctrl + d to open dump_file Microsoft (R) Windows Debugger Version 6.12.0002.633......

xueyuse0012
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部