接口和抽象类
1. 接口优于抽象类
Java 有两种机制来定义允许多个实现的类型:接口和抽象类。由于在 Java 8 中引入了接口的默认方法(default methods ),因此这两种机制都允许为某些实例方法提供实现。
- 一个主要的区别是要实现由抽象类定义的类型,类必须是抽象类的子类。因为 Java 只允许单一继承,所以对抽象类的这种限制严格限制了它们作为类型定义的使用。
- 任何定义所有必需方法并服从通用约定的类都可以实现一个接口,而不管类在类层次结构中的位置。
一个接口通常是定义允许多个实现的类型的最佳方式。如果你导出一个重要的接口,应该强烈考虑提供一个骨架的实现类。在可能的情况下,应该通过接口上的默认方法提供骨架实现,以便接口的所有实现者都可以使用它。也就是说,对接口的限制通常要求骨架实现类采用抽象类的形式。
骨架实现类的优点在于:
- 它们提供抽象类的所有实现的帮助,而不会强加抽象类作为类型定义时的严格约束。
- 对于具有骨架实现类的接口的大多数实现者来说,继承这个类是显而易见的选择,但它不是必需的。
- 如果一个类不能继承骨架的实现,这个类可以直接实现接口。该类仍然受益于接口本身的任何默认方法。
- 此外,骨架实现类仍然可以协助接口的实现。实现接口的类可以将接口方法的调用转发给继承骨架实现的私有内部类的包含实例。
- 这种被称为模拟多重继承的技术与包装类模式密切相关。它提供了多重继承的许多好处,同时避免了缺陷。
小结
- 抽象类,往往作为骨架类,声明模板方法,在抽象类中抽象业务的流程,子类可以根据具体场景的差异来实现父类的方法。
- 抽象类局限性更高,没有接口自由度高,接口可以自由组合使用。
- 抽象类相对接口而言,屏蔽了大部分父类的执行逻辑,使用起来,子类只需要关注扩展的部分即可,而无需去考虑父类的复杂设计。
2. 为后代设计接口
在 Java 8 之前,不可能在不破坏现有实现的情况下为接口添加方法。如果向接口添加了一个新方法,现有的实现通常会缺少该方法,从而导致编译时错误。在 Java 8 中,添加了默认方法(default method)构造,目的是允许将方法添加到现有的接口。但是增加新的方法到现有的接口是充满风险的。
默认方法的声明包含一个默认实现,该方法允许实现接口的类直接使用,而不必实现默认方法。虽然在 Java 中添加默认方法可以将方法添加到现有接口,但不能保证这些方法可以在所有已有的实现中使用。默认的方法被「注入(injected)」到现有的实现中,没有经过实现类的知道或同意。在 Java 8 之前,这些实现是用默认的接口编写的,它们的接口永远不会获得任何新的方法。
在默认方法的情况下,接口的现有实现类可以在没有错误或警告的情况下编译,但在运行时会失败。 虽然不是非常普遍,但这个问题也不是一个孤立的事件。在 Java 8 中添加到集合接口的一些方法已知是易受影响的,并且已知一些现有的实现会受到影响。
应该避免使用默认方法向现有的接口添加新的方法,除非这个需要是关键的,在这种情况下,你应该仔细考虑,以确定现有的接口实现是否会被默认的方法实现所破坏。然而,默认方法对于在创建接口时提供标准的方法实现非常有用,以减轻实现接口的任务。
因此,在发布之前测试每个新接口是非常重要的。多个程序员应该以不同的方式实现每个接口。至少,你应该准备三种不同的实现。编写多个使用每个新接口的实例来执行各种任务的客户端程序同样重要。这将大大确保每个接口都能满足其所有的预期用途。这些步骤将允许你在发布之前发现接口中的缺陷,但仍然可以轻松地修正它们。虽然在接口被发布后可能会修正一些存在的缺陷,但不要太指望这一点。
小结
- 接口的定义和修改应该慎重,尤其是公共开源包,尽可能避免使用默认方法来提升接口的兼容性,这样做,可能会导致方法覆盖的不确定性
3. 接口仅用来定义类型
当类实现接口时,该接口作为一种类型(type),可以用来引用类的实例。因此,一个类实现了一个接口,因此表明客户端可以如何处理类的实例。为其他目的定义接口是不合适的。
如果你想导出常量,有几个合理的选择方案。如果常量与现有的类或接口紧密相关,则应将其添加到该类或接口中。
代码语言:javascript复制package com.effectivejava.science;
public class PhysicalConstants {
private PhysicalConstants() { } // Prevents instantiation
public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
public static final double BOLTZMANN_CONST = 1.380_648_52e-23;
public static final double ELECTRON_MASS = 9.109_383_56e-31;
}
从 Java 7 开始,合法的下划线对数字字面量的值没有影响,但是如果使用得当的话可以使它们更容易阅读。无论是固定的浮点数,如果他们包含五个或更多的连续数字,考虑将下划线添加到数字字面量中。对于底数为 10 的数字,无论是整型还是浮点型的,都应该用下划线将数字分成三个数字组,表示一千的正负幂。
小结
- 不要试图通过接口来定义常量,这会导致实现细节泄露到导出的 API 包中。