当类实现接口时,该接口作为一种类型(type),可以用来引用类的实例。因此,一个类实现了一个接口,因此表明客户端可以如何处理类的实例。为其他目的定义接口是不合适的。
一种失败的接口就是所谓的常量接口(constant interface)。 这样的接口不包含任何方法; 它只包含静态 final 属性,每个输出一个常量。 使用这些常量的类实现接口,以避免需要用类名限定常量名。 这里是一个例子:
// Constant interface antipattern - do not use!
public interface PhysicalConstants {
// Avogadro's number (1/mol)
static final double AVOGADROS_NUMBER = 6.022_140_857e23;
// Boltzmann constant (J/K)
static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
// Mass of the electron (kg)
static final double ELECTRON_MASS = 9.109_383_56e-31;
}
常量接口模式是对接口的糟糕使用。 类在内部使用一些常量,完全属于实现细节。实现一个常量接口会导致这个实现细节泄漏到类的导出 API 中。对类的用户来说,类实现一个常量接口是没有意义的。事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一个承诺:如果在将来的版本中修改了类,不再需要使用常量,那么它仍然必须实现接口,以确保二进制兼容性。如果一个非 final 类实现了常量接口,那么它的所有子类的命名空间都会被接口中的常量所污染。
Java 平台类库中有多个常量接口,如 java.io.ObjectStreamConstants。
这些接口应该被视为不规范的,不应该被效仿。
如果你想导出常量,有几个合理的选择方案。 如果常量与现有的类或接口紧密相关,则应将其添加到该类或接口中。 例如,所有数字基本类型的包装类,如 Integer
和 Double
,都会导出 MIN_VALUE
和 MAX_VALUE
常量。 如果常量最好被看作枚举类型的成员,则应该使用枚举类型(详见第 34 条)导出它们。 否则,你应该用一个不可实例化的工具类来导出常量(详见第 4 条)。 下是前面所示的 PhysicalConstants
示例的工具类的版本:
// Constant utility class
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 的数字,无论是整型还是浮点型的,都应该用下划线将数字分成三个数字组,表示一千的正负幂。
通常,实用工具类要求客户端使用类名来限定常量名,例如 PhysicalConstants.AVOGADROS_NUMBER
。 如果大量使用实用工具类导出的常量,则通过使用静态导入来限定具有类名的常量:
// Use of static import to avoid qualifying constants
import static com.effectivejava.science.PhysicalConstants.*;
public class Test {
double atoms(double mols) {
return AVOGADROS_NUMBER * mols;
}
...
// Many more uses of PhysicalConstants justify static import
}
总之,接口只能用于定义类型。 它们不应该仅用于导出常量。
文章列表
- 高效Java编程-01. 考虑使用静态工厂方法替代构造方法
- 高效Java编程-02. 当构造方法参数过多时使用builder模式
- 高效Java编程-03. 使用私有构造方法或枚类实现Singleton属性
- 高效Java编程-04. 使用私有构造方法执行非实例化
- 高效Java编程-05. 依赖注入优于硬连接资源(hardwiring resources)
- 高效Java编程-06. 避免创建不必要的对象
- 高效Java编程-07. 消除过期的对象引用
- 高效Java编程-08. 避免使用Finalizer和Cleaner机制
- 高效Java编程-09. 使用try-with-resources语句替代try-finally语句
- 高效Java编程-10. 重写equals方法时遵守通用约定
- 高效Java编程-11. 重写equals方法时同时也要重写hashcode方法
- 高效Java编程-12. 始终重写 toString 方法
- 高效Java编程-13. 谨慎地重写 clone 方法
- 高效Java编程-14. 考虑实现Comparable接口
- 高效Java编程-15. 使类和成员的可访问性最小化
- 高效Java编程-16. 在公共类中使用访问方法而不是公共属性
- 高效Java编程-17. 最小化可变性
- 高效Java编程-18. 组合优于继承
- 高效Java编程-19. 要么设计继承并提供文档说明,要么禁用继承
- 高效Java编程-20. 接口优于抽象类
- 高效Java编程-21. 为后代设计接口
- 高效Java编程-22. 接口仅用来定义类型
- 高效Java编程-23. 类层次结构优于标签类
- 高效Java编程-24. 支持使用静态成员类而不是非静态类
- 高效Java编程-25. 将源文件限制为单个顶级类
- 高效Java编程-26. 不要使用原始类型
- 高效Java编程-27. 消除非检查警告
- 高效Java编程-28. 列表优于数组
- 高效Java编程-29. 优先考虑泛型
- 高效Java编程-30. 优先使用泛型方法
- 高效Java编程-31. 使用限定通配符来增加API的灵活性
- 高效Java编程-32. 合理地结合泛型和可变参数
- 高效Java编程-33. 优先考虑类型安全的异构容器
- 高效Java编程-34. 使用枚举类型替代整型常量
- 高效Java编程-35. 使用实例属性替代序数
- 高效Java编程-汇总