条目17:使可变性最小化
为什么要使可变性最小化?
- 不可变对象天然是线程安全的,可以在多个线程之间安全共享。而可变对象需要添加额外的同步机制保证线程安全。
- 不可变对象一旦创建就不会改变,便于追踪和理解代码。而可变对象的状态随时可能会改变,导致行为复杂化,增加了调试和维护难度
- 不可变对象的行为比较确定,没有意外状态变化,使得代码更加直观。
- 在传递不可变对象时,不用担心接收方会修改其内部状态,从而提高代码的健壮性和安全性。
总结:线程安全,简化维护和调试,提高可读性,安全性,健壮性
如何使类的可变性最小?
不要为对象提供修改状态的方法(确保类中没有
setter
方法或其他能改变内部状态的方法。)public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } }
确保类不能被继承(使用
final
关键字修饰类。)public final class ImmutableClass { // Class definition }
将所有字段声明为
final
(关键字final
确保字段在初始化后无法重新赋值。)public final class ImmutablePerson { private final String name; private final int age; public ImmutablePerson(String name, int age) { this.name = name; this.age = age; } }
将所有字段声明为
private
(使字段private
可以防止直接访问和修改,保证对象内部的封装性。即使是public final
字段,也可能被外部直接读取后进行操作,造成安全隐患。)public final class ImmutableConfig { private final String setting; public ImmutableConfig(String setting) { this.setting = setting; } public String getSetting() { return setting; } }
确保不会在构造过程中泄漏
this
(不要在构造函数中把this
引用传递给其他方法(包括工厂方法),因为对象可能还未完成初始化。)public class MutableClass { public MutableClass() { EventListener.register(this); // 可能在对象未完全构造时使用了它 } }
如果类包含可变组件,确保其不会被修改(如果类中包含其他可变对象(如数组或集合),需要在对外暴露时进行防护性拷贝(defensive copy)。)
public final class ImmutableWithArray { private final int[] array; public ImmutableWithArray(int[] array) { this.array = array.clone(); // 防护性拷贝 } public int[] getArray() { return array.clone(); // 防护性拷贝 } }
不可变类的权衡
性能开销
每次状态变化都需要创建新对象,会增加内存消耗,特别是在高频修改场景下(如复杂数值计算)。
String immutableString = "Hello"; immutableString = immutableString + " World"; // 创建了两个对象
不适合需要频繁更新状态的场景
如计数器或缓存等需要频繁更新的场景,更适合使用可变对象。
什么时候使用不可变类?
表示值的对象
如
String
、Integer
、BigDecimal
等值对象,天然适合不可变设计。作为 Map 或 Set 的键
不可变对象的
hashCode
和equals
不会随时间改变,适合作为集合的键。需要在多线程中共享的对象
不可变对象天然线程安全,无需同步。