Dating Java8系列之default默认方法

简介: Dating Java8系列之default默认方法


引言

传统上,Java程序的接口是将相关方法按照约定组合到一起。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。

不断迭代的API

默认方法的引入就是为了,以兼容的方式,解决像 Java API这样的类库,演进迭代问题。

理解演进迭代

为了理解为什么一旦API发布之后,它的演进就变得非常困难,我们假设你是一个Github上的开源作者,兴致勃勃的写了一个开源项目,然后放到了Github上面。

没过多久你的项目就被其他用户Fork到本地,然后开始使用了起来,并且在项目中对你发布的一些接口进行了实现。

发布API几个月之后,你突然意识到接口中遗漏了一些功能。需要调整原来的接口,在其中新增方法,这样的话接口的易用性会更好。

不过,事情并非如此简单!你要考虑已经使用了你接口的用户,他们可能已经按照自身的需求实现了你的接口,倘若你更新了接口的API并重新进行了发布,那么所以实现了你的接口的地方,都需要进行改动。

简而言之,向接口添加方法是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。如果你对接口以及它所有相关的实现有完全的控制,这可能不是个大问题。但是这种情况是极少的。

这就是引入默认方法的目的:它让类可以自动地继承接口的一个默认实现。

概述

1.默认方法

默认方法是Java 8中引入的一个新特性,希望能借此以兼容的方式改进API。现在,接口包含的方法签名在它的实现类中也可以不提供实现。那么,谁来具体实现这些方法呢?实际上,缺失的方法实现会作为接口的一部分由实现类继承(所以命名为默认实现),而无需由实现类提供。

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

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

这样任何一个实现了Sized接口的类都会自动继承isEmpty的实现。

2.使用默认方法

可选方法

你肯定碰到过这种情况,一个类实现了接口,不过却将一些实现方法进行留白,没有实现。

我们以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();        }}

通过这种方式,你可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再声明一个空的remove方法了,因为它现在已经有一个默认的实现。

行为的多继承
Java的类只能继承单一的类,但是一个类可以实现多接口。

下面是Java API中对ArrayList类的定义:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {        }

这个例子中ArrayList继承了一个类,实现了四个接口。因此ArrayList实际是 五个类型的直接子类,分别是:AbstractList,List,RandomAccess,Cloneable,Serializable。所以,在某种程度上,我们就有了类型的多继承。

由于Java 8中接口方法可以包含实现,类可以从多个接口中继承它们的行为(即实现的代码)。 让我们从一个例子入手,看看如何充分利用这种能力来为我们服务。

public interface MoveService {
    void run();
    default void flash() {        System.out.println("闪现!!!");    }}public interface SkillService {
    void q();
    void w();
    void e();
    default void r() {        System.out.println("默认大招:伤害100点");    }}public class Shooter implements MoveService, SkillService {    @Override    public void run() {        System.out.println("寒冰射手 走~~");    }    @Override    public void q() {        System.out.println("寒冰 q");    }    @Override    public void w() {        System.out.println("寒冰 w");    }    @Override    public void e() {        System.out.println("寒冰 e");    }    public void r(){        System.out.println("寒冰 伤害100点!!同时冰冻对方5s!!!");    }    public static void main(String[] args) {        new Shooter().r();    }}

方法冲突

我们知道Java语言中一个类只能继承一个类,但是一个类可以实现多个接口。

随着默认方法在Java 8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,在一个类中使用父类的默认方法,这样会有冲突吗,没有的话,那会选择哪一个呢?

1.解决冲突的三条规则

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

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

2.冲突示例

类中的方法优先级最高
public interface PlayerService {    default void stop() {        System.out.println("播放器--停止!!!");    }}
public class SonyPlayerServiceImpl implements PlayerService {    public void stop() {        System.out.println("Sony播放器--停止!!!");    }    public static void main(String[] args) {        new SonyPlayerServiceImpl().stop();    }}
选择提供了最具体实现的默认方法的接口
public interface PlayerService {    default void showLyric() {        System.out.println("PlayerService : show lyric");    }}public interface RecordService extends PlayerService{    default void showLyric() {        System.out.println("RecordService : show lyric");    }}public class Question1 implements RecordService {    public static void main(String[] args) {        new Question1().showLyric();        System.out.println("应该选择的是提供了最具体实现的默认方法的接口。由于RecordService比PlayerService更具体,所以应该选择由于RecordService比PlayerService更具体的showLyric方法。");    }}
冲突和显示的消除歧义
public class Question2 implements PlayerService,RecordService {    @Override    public void showLyric() {        PlayerService.super.showLyric();        RecordService.super.showLyric();    }
    public static void main(String[] args) {        new Question2().showLyric();    }}

小结

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

作者:翎野君

本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。

目录
相关文章
|
4月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
372 18
|
4月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
256 4
|
4月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
355 5
|
5月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
200 11
|
5月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
6月前
|
算法 Java
Java语言实现链表反转的方法
这种反转方法不需要使用额外的存储空间,因此空间复杂度为,它只需要遍历一次链表,所以时间复杂度为,其中为链表的长度。这使得这种反转链表的方法既高效又实用。
541 0
|
6月前
|
存储 Java 数据处理
Java映射操作:深入Map.getOrDefault与MapUtils方法
结合 `getOrDefault`方法的简洁性及 `MapUtils`的丰富功能,Java的映射操作变得既灵活又高效。合理地使用这些工具能够显著提高数据处理的速度和质量。开发人员可以根据具体的应用场景选择适宜的方法,以求在性能和可读性之间找到最佳平衡。
258 0
|
6月前
|
缓存 人工智能 NoSQL
Java中实现Token设置过期时间的方法
本文介绍了在Java应用中实现Token设置过期时间的多种方法,包括使用JWT和Redis缓存,并结合定时任务清理过期Token,以提升系统安全性与用户隐私保护。
714 0
|
6月前
|
算法 Java 开发者
Java 项目实战数字华容道与石头迷阵游戏开发详解及实战方法
本文介绍了使用Java实现数字华容道和石头迷阵游戏的技术方案与应用实例,涵盖GUI界面设计、二维数组操作、游戏逻辑控制及自动解法算法(如A*),适合Java开发者学习游戏开发技巧。
405 46
|
7月前
|
安全 Java API
Java 集合高级应用与实战技巧之高效运用方法及实战案例解析
本课程深入讲解Java集合的高级应用与实战技巧,涵盖Stream API、并行处理、Optional类、现代化Map操作、不可变集合、异步处理及高级排序等核心内容,结合丰富示例,助你掌握Java集合的高效运用,提升代码质量与开发效率。
331 0