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");
        }
    }
  • 如上只要有一个接口有抽象方法,那么我们就还得按照接口中抽象方法必须实现的规则来
  • 所以解决所有可能的冲突只需要三点

    • 类或父类中显示声明的方法,其优先级高于任何默认方法,即自己实现的比默认的优先级高
    • 如果上面的无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的接口,即用接口的最具体的子类的实现
    • 如果依旧有冲突,那么就只能在本类中重写覆盖默认方法了
目录
相关文章
|
6月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
386 1
|
7月前
|
Java API 容器
Java基础学习day08-2
本节讲解Java方法引用与常用API,包括静态、实例、特定类型方法及构造器引用的格式与使用场景,并结合代码示例深入解析。同时介绍String和ArrayList的核心方法及其实际应用。
217 1
|
6月前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
577 0
|
6月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
446 18
|
6月前
|
负载均衡 Java API
grpc-java 架构学习指南
本指南系统解析 grpc-java 架构,涵盖分层设计、核心流程与源码结构,结合实战路径与调试技巧,助你从入门到精通,掌握高性能 RPC 开发精髓。
645 9
|
6月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
310 4
|
7月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
239 11
|
6月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
416 5
|
7月前
|
Java
Java基础学习day08-作业
本作业涵盖Java中Lambda表达式的应用,包括Runnable与Comparator接口的简化实现、自定义函数式接口NumberProcessor进行加减乘及最大值操作,以及通过IntProcessor处理整数数组,实现遍历、平方和奇偶判断等功能,强化函数式编程实践。
120 5
|
7月前
|
Java 程序员
Java基础学习day08
本节讲解Java中的代码块(静态与实例)及其作用,深入介绍内部类(成员、静态、局部及匿名)的定义与使用,并引入函数式编程思想,重点阐述Lambda表达式及其在简化匿名内部类中的应用。
204 5