Java8实战-总结39

Java8实战-总结39

  • 默认方法
    • 解决冲突的规则
      • 解决问题的三条规则
      • 选择提供了最具体实现的默认方法的接口
      • 冲突及如何显式地消除歧义
      • 菱形继承问题
    • 小结

默认方法

解决冲突的规则

Java语言中一个类只能继承一个父类,但是一个类可以实现多个接口。随着默认方法在Java 8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,类会选择使用哪一个函数?在实际情况中,像这样的冲突可能极少发生,但是一旦发生这样的状况,必须要有一套规则来确定按照什么样的约定处理这些冲突。

Java编译器会解决这种潜在的冲突。例如“接下来的代码中,哪一个hello方法是被C类调用的”。接下来的例子主要用于说明容易出问题的场景,并不表示这些场景在实际开发过程中会经常发生。

	public interface A { default void hello() { System.out.println("Hello from A"); } } public interface B extends A { default void hello() { System.out.println("Hello from B"); } } public class C implements B, A { public static void main(String... args) { new C().hello(); } } 

菱形继承问题中一个类同时继承了具有相同函数签名的两个方法。到底该选择哪一个实现呢? Java 8提供了解决这个问题的方案。

解决问题的三条规则

如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。

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

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

回顾一下开头的例子,这个例子中C类同时实现了B接口和A接口,而这两个接口恰巧又都定义了名为hello的默认方法。另外,B继承自A。下图是这个场景的UML图。
在这里插入图片描述
按照规则2,编译器会使用选择的是提供了最具体实现的默认方法的接口。由于BA更具体,所以应该选择Bhello方法。所以,程序会打印输出“Hello from B”。

如果C像下面这样(如下图)继承自D,会发生什么情况:

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

在这里插入图片描述
依据规则1,类中声明的方法具有更高的优先级。D并未覆盖hello方法,可是它实现了接口A。所以它就拥有了接口A的默认方法。规则2说如果类或者父类没有对应的方法,那么就应该选择提供了最具体实现的接口中的方法。因此,编译器会在接口A和接口Bhello方法之间做选择。由于B更加具体,所以程序会再次打印输出“Hello from B”。

牢记这些判断的规则
在这个测验中继续复用之前的例子,唯一的不同在于D现在显式地覆盖了从A接口中
继承的hello方法。现在的输出会是什么呢?
public class D implements A { void hello() { System.out.println("Hello from D"); } 
} 
public class C extends D implements B, A { public static void main(String... args) { new C().hello(); } 
} 
答案:由于依据规则1,父类中声明的方法具有更高的优先级,所以程序会打印输出“Hello 
from D”。
注意,D的声明如下:
public abstract class D implements A { public abstract void hello(); 
} 
这样的结果是,虽然在结构上,其他的地方已经声明了默认方法的实现,C还是必须提供
自己的hello方法。

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

到目前为止,你看到的这些例子都能够应用前两条判断规则解决。让我们更进一步,假设B
不再继承A,(如下图所示):

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 { } 

这时规则2就无法进行判断了,因为从编译器的角度看没有哪一个接口的实现更加具体,两个都差不多。A接口和B接口的hello方法都是有效的选项。所以,Java编译器这时就会抛出一个编译错误,因为它无法判断哪一个方法更合适:Error: class C inherits unrelated defaults for hello()from types B and A.

在这里插入图片描述
冲突的解决
解决这种两个可能的有效方法之间的冲突,没有太多方案;只能显式地决定希望在C中使用哪一个方法。为了达到这个目的,可以覆盖类C中的hello方法,在它的方法体内显式地调用你希望调用的方法。Java 8中引入了一种新的语法X.super.m(…),其中X是希望调用的m方法所在的父接口。举例来说,如果希望C使用来自于B的默认方法,它的调用方式看起来就如下所示:

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

几乎完全一样的函数签名这个测试中,假设接口AB的声明如下所示:

public interface A { default Number getNumber() { return 10; } 
} 
public interface B { default Integer getNumber(){ return 42; } 
} 

C的声明如下:

public class C implements B, A { public static void main(String... args) { System.out.println(new C().getNumber()); } 
} 

这个程序的会打印输出什么呢?
答案:类C无法判断A或者B到底哪一个更加具体。这就是类C无法通过编译的原因。

菱形继承问题

最后一种场景,亦是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(); } } 

下图以UML图的方式描述了出现这种问题的场景。这种问题叫“菱形问题”,因为类的继承关系图形状像菱形。这种情况下类D中的默认方法到底继承自什么地方 ——源自B的默认方法,还是源自C的默认方法?实际上只有一个方法声明可以选择。只有A声明了一个默认方法。由于这个接口是D的父接口,代码会打印输出“Hello from A”。
在这里插入图片描述
现在,看看另一种情况,如果B中也提供了一个默认的hello方法,并且函数签名跟A中的方法也完全一致,这时会发生什么情况呢?根据规则2,编译器会选择提供了更具体实现的接口中的方法。由于BA更加具体,所以编译器会选择B中声明的默认方法。如果BC都使用相同的函数签名声明了hello方法,就会出现冲突,正如我们之前所介绍的,需要显式地指定使用哪个方法。

如果在C接口中添加一个抽象的hello方法(这次添加的不是一个默认方法),会发生什么情况呢?

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

这个新添加到C接口中的抽象方法hello比由接口A继承而来的hello方法拥有更高的优先级,因为C接口更加具体。因此,类D现在需要为hello显式地添加实现,否则该程序无法通过编译。

C++语言中的菱形问题
C++语言中的菱形问题要复杂得多。首先,C++允许类的多继承。默认情况下,如果类D
继承了类B和类C,而类B和类C又都继承自类A,类D实际直接访问的是B对象和C对象的副本。
最后的结果是,要使用A中的方法必须显式地声明:这些方法来自于B接口,还是来自于C接口。
此外,类也有状态,所以修改B的成员变量不会在C对象的副本中反映出来。

如果一个类的默认方法使用相同的函数签名继承自多个接口,解决冲突的机制其实相当简单。只需要遵守下面这三条准则就能解决所有可能的冲突。

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

小结

  • Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。
  • 默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
  • 向发布的接口添加抽象方法不是源码兼容的。
  • 默认方法的出现能帮助库的设计者以后向兼容的方式演进API
  • 默认方法可以用于创建可选方法和行为的多继承。
  • 有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
  • 类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
  • 两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/129375.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

arm-三盏灯流水

.text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0x50000A28 LDR R1,[R0] ORR R1,R1,#(0x3<<4) 第四位第五位都设置为1 STR R1,[R0] 写回2.设置PE10管脚为输出模式 GPIOE_MODER[21:20]->01 0x5000…

数据结构和算法——线性结构

文章目录 前言线性表顺序表链表合并有序链表反转链表 队列循环队列双端队列资源分配问题 栈共享栈表达式求值递归处理迷宫问题 串串的模式匹配BF算法KMP算法next数组的求解next数组的优化 前言 本文所有代码均在仓库中&#xff0c;这是一个完整的由纯C语言实现的可以存储任意类…

【MySQL】基本查询(三)聚合函数+group by

文章目录 一. 聚合函数二. group by子句结束语 建立如下表 //创建表结构 mysql> create table exam_result(-> id int unsigned primary key auto_increment,-> name varchar(20) not null comment 同学姓名,-> chinese float default 0.0 comment 语文成绩,->…

Go 语言中 panic 和 recover 搭配使用

本次主要聊聊 Go 语言中关于 panic 和 recover 搭配使用 &#xff0c;以及 panic 的基本原理 最近工作中审查代码的时候发现一段代码&#xff0c;类似于如下这样&#xff0c;将 recover 放到一个子协程里面&#xff0c;期望去捕获主协程的程序异常 看到此处&#xff0c;是否会…

内网渗透之哈希传递

文章目录 哈希传递&#xff08;NTLM哈希&#xff09;概念LMNTLM 原理利用hash传递获取域控RDP 总结 哈希传递&#xff08;NTLM哈希&#xff09; 内网渗透中找到域控IP后使用什么攻击手法拿下域控&#xff1a; 扫描域控开放端口。因为域控会开放远程连接&#xff1a;windows开…

常见排序算法Java版(待续)

冒泡排序O(n^2) public class Main {public static void main(String[] args) {Random random new Random();int[] nums new int[]{random.nextInt(100), random.nextInt(100), random.nextInt(100), random.nextInt(100), random.nextInt(100), random.nextInt(100)};for (i…

SQL Server 简介与 Docker Compose 部署

今天我翻阅了在之前公司工作时的笔记&#xff0c;发现了有关数据库的一些记录。当时&#xff0c;我们的项目开始使用 Oracle 数据库&#xff0c;但后来由于一些项目需求的变更&#xff0c;我们切换到了 SQL Server 。值得一提的是&#xff0c;公司当时也开始采用 Docker 技术&a…

多路彩灯控制器led流水灯VHDL速度可调仿真图视频、源代码

名称&#xff1a;多路彩灯控制器led流水灯VHDL速度可调 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 使用VHDL设计彩灯控制器&#xff0c;共24个led灯&#xff0c;分为5种不同的花样&#xff0c;可以通过按键切换花样的变化速度。 代码下载&#…

设计模式 - 行为型模式:策略模式(概述 | 案例实现 | 优缺点 | 使用场景)

目录 一、行为型模式 1.1、策略模式 1.1.1、概论 1.1.2、案例实现 1.1.3、优缺点 1.1.4、使用场景 一、行为型模式 1.1、策略模式 1.1.1、概论 策略模式设计的每一个算法都封装了起来&#xff0c;使他们可以相互替换&#xff0c;通过一个对象委派不同的算法给相应的客户…

vscode ssh linux C++ 程序调试

vscode调试c++程序相比vs2022要复杂很多,vs2022可以"一键运行调试",vscode则需要自己配置。 ​vscode调试程序时,会在当前工作目录产生.vscode 目录, 该目录有两个重要文件launch.json和tasks.json, 下面介绍两种调试方法: 手动调试和自动调试。 手动调试 不管…

用 HTTP 提交数据,基本就这 5 种方式

网页开发中&#xff0c;向服务端提交数据是一个基本功能&#xff0c;工作中会大量用 xhr/fetch 的 api 或者 axios 这种封装了一层的库来做。 可能大家都写过很多 http/https 相关的代码&#xff0c;但是又没有梳理下它们有哪几种呢&#xff1f; 其实通过 http/https 向服务端…

Nosql redis高可用和持久化

Nosql redis高可用和持久化 1、redis高可用2、redis持久化2.1redis持久化2.2Redis 持久化方法2.3RDB 持久化2.3.1RDB持久化工作原理2.3.2触发条件2.3.3其他自动触发机制2.3.4执行流程2.3.5启动时加载 2.4AOF 持久化2.4.1AOF持久化原理2.4.2开启AOF2.4.3执行流程2.4.4文件重写的…