java8学习:默认方法

简介:

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战

  • 在java8中接口引进了静态方法以及默认方法,通过默认方法,可以为接口方法提供默认实现,也就是说接口能提供方法的具体实现.因此实现接口的类如果不显示的提供该方法的具体实现,就会自动继承默认的实现
  • 同时定义接口以及工具辅助类是java常用的一种模式,工具类定义了与接口实例协作的很多静态方法,由于静态方法现在可以存在接口内部,所以代码中的辅助类就没有了存在的必要,可以直接把这些静态方法转移到接口内部

不断演进的API

  • 我们来说一下为什么会出现默认方法
  • 假如有如下一个接口,你是类库的设计者,那么你写的接口是这样的
public interface Behavior {
    void eat(String foodName);
}
  • 然后你发布之后大火,你的用户是这样使用的
public class Dog implements Behavior {
    @Override
    public void eat(String foodName) {
        System.out.println("dog " + foodName);
    }
}
  • 之后你收到了很多意见,说这个行为接口并不丰富,行为不可能只有吃的行为,动物可是都有吃喝拉撒的行为啊!
  • 现在你就遇到了问题,如果你将建议的接口添加入Behavior接口,然后发布,这时候你的用户只要更新API的版本,那么他就会遇到一个大麻烦,那么就是实现你在接口中定义的所有方法.这对用户来说是非常不好的.这也是默认方法产生的原因:它可以让你放心的改进接口,无须担心遗留代码的影响,这是因为实现更新接口的类都会自动的集成一个默认的方法实现

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

  • 变更对java的影响大体可以分为三种类型的兼容:二进制级的兼容,源代码级的兼容,以及函数行为的兼容.

    • 二进制级的兼容性表示现有的二进制执行文件能无缝持续链接和运行,比如,为接口添加一个方法就是二进制级的兼容,这种方式下,如果添加的新方法不被调用,接口已经实现的方法可以继续运行,不会出现错误
    • 简单的说,源代码级的兼容性表示引入变化之后,现有的程序依然能够成功通过编译
    • 函数行为的兼容性表示发生变更后,程序接受同样的输入能得到相同的结果

概述默认方法

  • 默认方法就是用default修饰的方法,并像类中声明的其他方法一样包含方法体,并且只要类实现了这个包含默认方法的接口,他就会继承默认方法
  • java8中的抽象类和抽象接口区别:首先一个类只能继承一个抽象类,但是一个类可以实现多个接口,其次,一个抽象类可以通过实例变量保存一个通用状态,而接口是不能有实例变量的

默认方法的使用模式

  • 可选模式

    • 平常我们用类实现一个接口,接口中有很多方法需要我们重新定义,如果有用的方法还好,如果我们并用不到的方法,为了满足接口方法的实现规则,我们就必须在那放一个空方法实现,这也是多余的模板代码,我们可以将这类方法变更为默认方法以实现不必要的空实现
  • 行为的多继承

    • 行为的多继承值得是:类只能继承一个类,但是可以实现多个接口中的方法,这就是所谓的多继承,现在java8中有了方法的默认实现,那么我们的类就得到了来自不同接口的实现的功能

解决冲突的规则

  • 一个类实现了多个接口,而多个接口中含有覆盖实现的方法,那么类会使用那个接口中的方法呢?如果是多个接口中的方法都是相同的方法签名呢?

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        @Override
        default void say() {
            System.out.println("B");
        }
    }
    public class Dog implements B{
        public static void main(String[] args) {
            new Dog().say(); //B
        }
    }
  • 如果一个类使用相同的函数签名从多个地方继承了方法,通过三条规则可以进行判断

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

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        @Override
        default void say() {
            System.out.println("B");
        }
    }
    class D implements A{
    
    }
    public class Dog extends D implements A,B{
        public static void main(String[] args) {
            new Dog().say(); //B
        }
    }
    • 依照类或父类中声明的方法的优先级高于任何声明为默认方法的优先级,那么就会优先选择D,那么D并没有覆盖掉say方法,但是他默认继承了A的方法,所以它会调用A的方法,但是他会选择更具体的实现,那么就是B,最终也是输出的B

菱形继承问题

interface A{
    default void say(){
        System.out.println("A");
    }
}
interface B extends A {

}
interface C extends A{

}
public class Dog implements B,C{
    public static void main(String[] args) {
        new Dog().say(); //A
    }
}
  • 如上,其继承实现关系类似于菱形,Dog实现了B,C但是BC中只是继承了A中的默认方法,所以编译并不会出错,并且Dog也是使用此默认实现,所以输出A
  • 如果我们将B加上覆盖实现呢?

    interface B extends A {
        default void say(){
            System.out.println("B");
        }
    }
  • 现在会输出B,因为覆盖实现的方法更加具体
  • 如果两个BC接口都覆盖实现了say方法呢?

    //B接口如上变动
    interface C extends A{
        default void say(){
            System.out.println("C");
        }
    }
  • 这时候我们就会发现,Dog编译出错了,因为BC都有具体实现,并且都是一个级别的,都是具体实现了A的默认方法,Dog就不知道需要调用谁的方法了,这时候我们只能是在Dog中覆盖这个默认方法的实现了

    public class Dog implements B,C{
        public static void main(String[] args) {
            new Dog().say(); //dog
        }
        @Override
        public void say() {
            System.out.println("dog");
        }
    }
  • 如果BC接口只是定义的与A中say方法签名一样的抽象方法呢?

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        void say();
    }
    interface C extends A{
        default void say(){
            System.out.println("C");
        }
    }
    public class Dog implements B,C{
        public static void main(String[] args) {
            new Dog().say(); //dog
        }
        @Override
        public void say() {
            System.out.println("dog");
        }
    }
  • 如上只要有一个接口有抽象方法,那么我们就还得按照接口中抽象方法必须实现的规则来
  • 所以解决所有可能的冲突只需要三点

    • 类或父类中显示声明的方法,其优先级高于任何默认方法,即自己实现的比默认的优先级高
    • 如果上面的无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口,即用接口的最具体的子类的实现
    • 如果依旧有冲突,那么就只能在本类中重写覆盖默认方法了
目录
相关文章
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
2月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
84 9
|
2月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
47 4
|
2月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
57 4
|
2月前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
27 1
|
3月前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
62 17
|
2月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
139 4
|
2月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
319 2
|
2月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。