Java类与接口设计的最佳实践
立即解锁
发布时间: 2025-08-18 00:28:59 阅读量: 15 订阅数: 21 


Effective Java:编写高效Java代码的最佳实践
### Java 类与接口设计的最佳实践
#### 1. 最小化类和成员的可访问性
在 Java 编程中,设计良好的组件与设计不佳的组件的一个重要区别在于组件对内部数据和实现细节的隐藏程度。信息隐藏,也称为封装,是软件设计的基本原则,它将系统中的组件解耦,带来诸多好处:
- **加速系统开发**:组件可以并行开发。
- **简化维护工作**:能更快理解组件,调试或替换时不用担心影响其他组件。
- **支持性能调优**:系统完成后可对有性能问题的组件进行优化,不影响其他组件的正确性。
- **提高软件复用性**:松耦合的组件在其他场景也可能有用。
- **降低构建大型系统的风险**:即使系统整体不成功,单个组件仍可能成功。
Java 的访问控制机制通过访问修饰符(`private`、`protected` 和 `public`)来指定类、接口和成员的可访问性。使用这些修饰符的基本原则是:让每个类或成员尽可能不可访问,即使用与软件正常运行一致的最低访问级别。
##### 1.1 顶级类和接口的访问级别
顶级(非嵌套)类和接口只有两种可能的访问级别:包私有(package - private)和公共(public)。如果一个顶级类或接口可以设为包私有,就应该这样做,因为这样它就是实现的一部分,而不是导出的 API,后续版本中可以修改、替换或移除而不影响现有客户端。如果设为公共,则必须永远支持它以保持兼容性。
如果一个包私有顶级类或接口只被一个类使用,可以考虑将其设为使用它的类的私有静态嵌套类,进一步降低其可访问性。
##### 1.2 成员的访问级别
成员(字段、方法、嵌套类和嵌套接口)有四种可能的访问级别,按可访问性递增顺序排列如下:
| 访问级别 | 描述 |
| ---- | ---- |
| private | 成员只能从声明它的顶级类中访问。 |
| package - private | 成员可以从声明它的包中的任何类访问。如果未指定访问修饰符(接口成员默认是 public 除外),就是这个访问级别。 |
| protected | 成员可以从声明它的类的子类(有一些限制)以及声明它的包中的任何类访问。 |
| public | 成员可以从任何地方访问。 |
设计类的公共 API 后,应将其他成员设为私有。只有当同一包中的另一个类确实需要访问某个成员时,才将其访问级别提升为包私有。对于公共类的成员,从包私有提升到受保护会大幅增加可访问性,受保护成员是类导出 API 的一部分,必须永远支持,这种情况应该相对少见。
有一个关键规则限制了降低方法可访问性的能力:如果一个方法重写了超类方法,它在子类中的访问级别不能比在超类中更严格。如果一个类实现了一个接口,接口中的所有类方法在类中必须声明为 public。
为了便于测试代码,可以将公共类的私有成员设为包私有,但不能再提高其可访问性。公共类的实例字段很少应该是公共的,如果实例字段是非 final 或引用可变对象,将其设为公共会失去对字段值的控制,还可能导致线程安全问题。公共静态字段通常也不应该是公共的,但可以通过公共静态 final 字段暴露常量,前提是这些常量是类提供的抽象的组成部分,且这些字段必须包含基本值或对不可变对象的引用。
对于公共静态 final 数组字段,由于非零长度数组总是可变的,将其设为公共会导致安全漏洞。可以通过以下两种方式修复:
```java
// 方式一:将公共数组设为私有并添加公共不可变列表
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
// 方式二:将数组设为私有并添加返回私有数组副本的公共方法
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
```
从 Java 9 开始,模块系统引入了两个额外的隐式访问级别。模块是包的分组,模块可以通过模块声明中的导出声明显式导出其部分包。模块中未导出包的公共和受保护成员在模块外部不可访问,但在模块内部不受导出声明影响。不过,模块提供的访问保护对普通 Java 程序员的实用性有限,目前除非有迫切需要,否则最好避免使用。
综上所述,应尽可能降低程序元素的可访问性。设计好最小公共 API 后,防止任何无关的类、接口或成员成为 API 的一部分。除了作为常量的公共静态 final 字段外,公共类不应有公共字段,要确保公共静态 final 字段引用的对象是不可变的。
#### 2. 在公共类中使用访问器方法,而不是公共字段
有时可能会编写只用于组合实例字段的退化类,例如:
```java
// 像这样的退化类不应该是公共的!
class Point {
public double x;
public double y;
}
```
由于这类类的数据字段是直接访问的,它们不具备封装的优点,无法在不改变 API 的情况下更改表示形式,无法强制执行不变式,也无法在访问字段时采取辅助操作。对于公共类,应该使用私有字段和公共访问器方法(getters),对于可变类,还应使用修改器方法(setters),例如:
```java
// 通过访问器方法和修改器方法封装数据
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
```
如果一个类在其包外部可访问,提供访问器方法可以保留更改类内部表示的灵活性。如果公共类暴露其数据字段,就失去了更改表示形式的希望,因为客户端代码可能分布广泛。
然而,如果一个类是包私有或私有嵌套类,暴露其数据字段本身并没有问题,前提是它们能充分描述类提供的抽象。这种方法在类定义和使用它的客户端代码中产生的视觉干扰比使用访问器方法少。如果需要更改表示形式,只需在包内修改代码。
Java 平台库中的一些类违反了公共类不应直接暴露字段的建议,例如 `java.awt` 包中的 `Point` 和 `Dimension` 类。`Dimension` 类暴露内部细节的决定导致了严重的性能问题,至今仍存在。
虽然公共类直接暴露字段通常不是好主意,但如果字段是不可变的,危害会
0
0
复制全文
相关推荐










