• 《Java8实战》读书笔记08:接口的默认方法


    第9章 默认方法

     什么是默认方法
     如何以一种兼容的方式改进API
     默认方法的使用模式
     解析规则
    默认方法的引入就是为了以兼容的方式解决像Java API这样的类库的演进问题的,如图9-1所示。
    在这里插入图片描述

    静态方法及接口(打配合)
    同时定义接口以及工具辅助类(companion class)是Java语言常用的一种模式,工具类定义了与接口实例协作的很多静态方法。比如,Collections就是处理Collection对象的辅助类。由于静态方法可以存在于接口内部,你代码中的这些辅助类就没有了存在的必要,你可以把这些静态方法转移到接口内部。为了保持后向的兼容性,这些类依然会存在于Java应用程序的接口之中。
    .
    (这段的意思是接口有了默认方法,以后要抛弃工具类,自己玩了。现在那些工具类留着只是保持兼容而已,迟早要削藩的。)

    9.1 不断演进的 API

    1. 你发布了一个类库包含 XX接口。你的库很火,被无数人引用。很多人在自己的代码中实现了XX接口
    2. 随后你发布了新版,并对XX接口进行了扩展,加了个某方法。
    3. 此时所有引用你的项目在编译时都崩了,因为它们都没有实现你新加的方法。
      大多数人并不认为你新加的方法有什么卵用,所以骂骂咧咧的移除了你的换了别的方案,免得下回还让你背刺。部分因你这招偷袭导致生产事故的码农甚至给你寄来了精美的刀片。
    4. 然后你也妥协了,拉出一个分支版本。单独进行升级,但是小伙伴们并没有兴趣在自己的项目里引入两坨垃圾。
    5. 因此Java8默认方法应运而生。

    不同类型的兼容性:二进制、源代码和函数行为

    • 变更对Java程序的影响大体可以分成三种类型的兼容性,分别是:二进制级的兼容、源代码级的兼容,以及函数行为的兼容。①刚才我们看到,向接口添加新方法是二进制级的兼容,但最终编译实现接口的类时却会发生编译错误。了解不同类型兼容性的特性是非常有益的,下面我们会深入介绍这部分的内容。
    • 二进制级的兼容性表示现有的二进制执行文件能无缝持续链接(包括验证、准备和解析)和运行。比如,为接口添加一个方法就是二进制级的兼容,这种方式下,如果新添加的方法不被调用,接口已经实现的方法可以继续运行,不会出现错误。
    • 简单地说,源代码级的兼容性表示引入变化之后,现有的程序依然能成功编译通过。比如,向接口添加新的方法就不是源码级的兼容,因为遗留代码并没有实现新引入的方法,所以它们无法顺利通过编译。(默认方法就源代码级兼容的
    • 最后,函数行为的兼容性表示变更发生之后,程序接受同样的输入能得到同样的结果。比如,为接口添加新的方法就是函数行为兼容的,因为新添加的方法在程序中并未被调用(抑或该接口在实现中被覆盖了)。

    9.2 概述默认方法

    1. 默认方法default修饰符修饰。
    2. 接口中的默认方法像类中声明方法一样包含方法体
    3. 任何一个实现了接口的类都会自动继承默认方法的实现。

    Java 8中的抽象类和抽象接口
    那么抽象类和抽象接口之间的区别是什么呢?它们不都能包含抽象方法和包含方法体的实现吗?
    首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
    其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。(但是可以实现匿名类啊)

    Java8今天能给接口加默认方法,那明天Java80会不会放开多继承呢?不好说!单纯的从语法上来区分它,会变的越来越像文字游戏。我们应该去理解抽象类接口语义

    抽象类接口
    语义a 继承 b
    表示:a是(某种)b
    a 实现 b、c、d
    表示:a具备了b、c、d(某种能力)
    继承/实现单继承单继承/多继承

    语法方法面的问题 IDE 都会有提示,实在记不住也没关系,写的多了自然就记得了。

    1. 类可以从多个接口继承默认方法。

    书中举例:所有的 Collection 类都实现了名为 java.util.Collection 的接口。通过向此接口中添加默认方法实现统一向所有集合类添加 removeIf 方法。

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    9.3 默认方法的使用模式

    9.3.1 可选方法

    有些方法是可以忽略的,以前每个实现类都要提供一个空方法覆盖一下,现在可以在接口里直接用默认方法搞定了。

    9.3.2 行为的多继承

    现在有接口有了默认方法也就引入了多继承的问题。
    其实正常的多继承没什么好解释的。我们要关心的只是当方法签名冲突时是怎么解决的 。
    在这里插入图片描述

    关于继承的一些错误观点

    • 继承不应该成为你一谈到代码复用就试图倚靠的万精油。比如,从一个拥有100个方法及字段的类进行继承就不是个好主意,因为这其实会引入不必要的复杂性。你完全可以使用代理有效地规避这种窘境,即创建一个方法通过该类的成员变量直接调用该类的方法。这就是为什么有的时候我们发现有些类被刻意地声明为final类型:声明为final的类不能被其他的类继承,避免发生这样的反模式,防止核心代码的功能被污染。注意,有的时候声明为final的类都会有其不同的原因,比如,String类被声明为final,因为我们不希望有人对这样的核心功能产生干扰。
    • 这种思想同样也适用于使用默认方法的接口。通过精简的接口,你能获得最有效的组合,因为你可以只选择你需要的实现。

    总结一下就是:(绕来绕去还是设计模式)

    1. 能用组合就不用继承,因为继承是强耦合。(依赖倒置、合成复用)
    2. 小而全的接口,用来拼装,就可能方便的组合出具备各种能力的类。(单一职责、接口隔离)

    9.4 解决冲突的规则

    9.4.1 ★ 解决问题的三条规则

    (1) 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优
    先级。
    (2) 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择
    拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
    (3) 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
    我们保证,这些就是你需要知道的全部!让我们一起看几个例子。

    9.4.2 选择提供了最具体实现的默认方法的接口

    在这里插入图片描述
    于B比A更具体,所以应该选择B的hello方法。所以,程序会打印输出Hello from B

    在这里插入图片描述

    1. 首先找 D 但 D 没有实现 hello
    2. 再看 A、B ,B更具体打印输出Hello from B

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

    public interface A { 
    	void hello() { 
    		System.out.println("Hello from A"); 
    	} 
    } 
    public interface B { 
    	void hello() { 
    		System.out.println("Hello from B"); 
    	} 
    } 
    public class C implements B, A { }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    A、B两条线优先级完全相同,必须显示调用。否则报错Error: class C inherits unrelated defaults for hello() from types B and A.

    ★ 冲突的解决

    Java 8中引入了一种新的语法X.super.m(…),其中X是你希望调用的m方法所在的父接口

    public class C implements B, A { 
    	void hello(){ 
    		B.super.hello(); 
    	} 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    9.4.4 菱形继承问题

     首先,类或父类中显式声明的方法,其优先级高于所有的默认方法。
     如果用第一条无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口。
     最后,如果冲突依旧无法解决,你就只能在你的类中覆盖该默认方法,显式地指定在你的类中使用哪一个接口中的方法。

    感觉这节在凑字数。说了半天都是前面的话,Java根本不存在菱形继承。把C++拖出来骂了一顿。。。

    9.5 小结

    下面是本章你应该掌握的关键概念。

    1. Java 8中的接口可以通过默认方法静态方法提供方法的代码实现。
    2. 默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
    3. 向发布的接口添加抽象方法不是源码兼容的。
    4. 默认方法的出现能帮助库的设计者以后向兼容的方式演进API。
    5. 默认方法可以用于创建可选方法和行为的多继承
    6. 我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
    7. 类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
    8. 两个默认方法同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。
  • 相关阅读:
    es6(三)——常用es6(函数、数组、对象的扩展)
    《MongoDB入门教程》第07篇 CRUD之查找文档
    网络安全(黑客)自学
    Java后端面试:MySQL面试篇(底层事务、SQL调优)
    加拿大便携式火炉ASTM F3363-19(适用于燃烧液体或凝胶燃料的便携式无排气口装置的标准规范)标准解答
    linux-log系统日志输出等级
    Day 45 | 70. 进阶爬楼梯 & 322. 零钱兑换 & 279.完全平方数
    前端性能优化心法
    wangEditor富文本编辑器的调用开发实录(v5版本、多个编辑器、php后端上传视频阿里云OSS、编辑HTML回显)
    【SQL语法基础】如何使用Python操作MySQL?
  • 原文地址:https://blog.csdn.net/jx520/article/details/125464202