《java 8 实战》读书笔记 -第九章 默认方法

若是在现存的接口上引入了很是多的新方法,全部的实现类都必须进行改造,实现新方法,为了解决这个问题,Java 8为了解决这一问题引入了一种新的机制。Java 8中的接口如今支持在声明方法的同时提供实现,这听起来让人惊讶!经过两种方式能够完成这种操做。其一,Java 8容许在接口内声明静态方法。其二,Java 8引入了一个新功能,叫默认方法,经过默认方法你能够指定接口方法的默认实现。函数

静态方法能够存在于接口内部

1、不断演进的API

默认方法试它让类库的设计者放心地改进应用程序接口,无需担心对遗留代码的影响,这是由于实现更新接口的类如今会自动继承一个默认的方法实现。spa

不一样类型的兼容性:二进制、源代码和函数行为
变动对Java程序的影响大致能够分红三种类型的兼容性,分别是:二进制级的兼容、源代码级的兼容,以及函数行为的兼容。设计

  • 向接口添加新方法是二进制级的兼容,但最终编译实现接口的类时却会发生编译错误。二进制级的兼容性表示现有的二进制执行文件能无缝持续连接(包括验证、准备和解析)和运行。好比,为接口添加一个方法就是二进制级的兼容,这种方式下,若是新添加的方法不被调用,接口已经实现的方法能够继续运行,不会出现错误。
  • 简单地说,源代码级的兼容性表示引入变化以后,现有的程序依然能成功编译经过。
  • 最后,函数行为的兼容性表示变动发生以后,程序接受一样的输入能获得一样的结果。好比,为接口添加新的方法就是函数行为兼容的,由于新添加的方法在程序中并未被调用(抑或该接口在实现中被覆盖了)。

2、概述默认方法

默认方法由default修饰符修饰,并像类中声明的其余方法同样包含方法体。好比,你能够像下面这样在集合库中定义一个名为Sized的接口,在其中定义一个抽象方法size,以及一个默认方法isEmpty:代理

public interface Sized { 
 int size(); 
 default boolean isEmpty() { 
 return size() == 0; 
 } 
}

第3章介绍的不少函数式接口,好比Predicate、Function以及Comparator也引入了新的默认方法,好比Predicate.and或者Function.andThen(记住,函数式接口只包含一个抽象方法,默认方法是种非抽象方法)。code

个抽象类能够经过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。

3、默认方法的使用模式

1.可选方法

你极可能也碰到过这种状况,类实现了接口,不过却刻意地将一些方法的实现留白。咱们以Iterator接口为例来讲。Iterator接口定义了hasNext、next,还定义了remove方法。Java 8以前,因为用户一般不会使用该方法,remove方法常被忽略。所以,实现Interator接口的类一般会为remove方法放置一个空的实现,这些都是些毫无用处的模板代码。继承

采用默认方法以后,你能够为这种类型的方法提供一个默认的实现,这样实体类就无需在本身的实现中显式地提供一个空方法。好比,在Java 8中,Iterator接口就为remove方法提供了一个默认实现,以下所示:接口

interface Iterator<T> { 
 boolean hasNext(); 
 T next(); 
 default void remove() { 
 throw new UnsupportedOperationException(); 
 }

2.行为的多继承

  1. 类型的多继承
  2. 利用正交方法的精简接口
  3. 组合接口
继承不该该成为你一谈到代码复用就试图倚靠的万精油。好比,从一个拥有100个方法及字段的类进行继承就不是个好主意,由于这其实会引入没必要要的复杂性。你彻底可使用代理有效地规避这种窘境,即建立一个方法经过该类的成员变量直接调用该类的方法。

4、解决冲突的规则

1.解决问题的三条规则

若是一个类使用相同的函数签名从多个地方(好比另外一个类或接口)继承了方法,经过三条规则能够进行判断。图片

  • (1)类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
  • (2)若是没法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即若是B继承了A,那么B就比A更加具体。
  • (3)最后,若是仍是没法判断,继承了多个接口的类必须经过显式覆盖和调用指望的方法,显式地选择使用哪个默认方法的实现。

2.冲突及如何显式地消除歧义

对于上面提到的第三种状况,解决这种两个可能的有效方法之间的冲突,没有太多方案;你只能显式地决定你但愿在C中使用哪个方法。为了达到这个目的,你能够覆盖类C中的hello方法,在它的方法体内显式地调用你但愿调用的方法。Java 8中引入了一种新的语法X.super.m(…),其中X是你但愿调用的m方法所在的父接口。举例来讲,若是你但愿C使用来自于B的默认方法,它的调用方式看起来就以下所示:rem

public class C implements B, A { 
 void hello(){ 
 B.super.hello(); 
 } 
}

3.菱形继承问题

让咱们考虑最后一种场景,它亦是C++里中最使人头痛的难题。编译器

public interface A{ 
 default void hello(){ 
 System.out.println("Hello from A"); 
 } 
} 

public interface B extends A { } 

public interface C extends A { } 

public class D implements B, C { 
 public static void main(String... args) { 
 new D().hello(); 
 } 
}

这种问题叫“菱形问题”,由于类的继承关系图形状像菱形。这种状况下类D中的默认方法到底继承自什么地方 ——源自B的默认方法,仍是源自C的默认方法?实际上只有一个方法声明能够选择。只有A声明了一个默认方法。因为这个接口是D的父接口,代码会打印输出“Hello from A”。
图片描述
如今,咱们看看另外一种状况,若是B中也提供了一个默认的hello方法,而且函数签名跟A中的方法也彻底一致,这时会发生什么状况呢?根据规则(2),编译器会选择提供了更具体实现的接口中的方法。因为B比A更加具体,因此编译器会选择B中声明的默认方法。若是B和C都使用相同的函数签名声明了hello方法,就会出现冲突,正如咱们以前所介绍的,你须要显式地指定使用哪一个方法。顺便提一句,若是你在C接口中添加一个抽象的hello方法(此次添加的不是一个默认方法),会发生什么状况呢?你可能也想知道答案。

public interface C extends A { 
 void hello(); 
}

这个新添加到C接口中的抽象方法hello比由接口A继承而来的hello方法拥有更高的优先级,由于C接口更加具体。所以,类D如今须要为hello显式地添加实现,不然该程序没法经过编译

相关文章
相关标签/搜索