【Effective Java 17】使可变性最小化
不可变类是指其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期(lifetime)内固定不变。Java 平台类库中包含许多不可变的类,其中有 String
、基本类型的包装类、BigInteger
和 BigDecimal
。存在不可变的类有许多理由:不可变的类比可变类更加易于设计、实现和使用。它们不容易出错,且更安全。
1. 使类成为不可变,要遵循下面五条原则
- 不要提供任何会修改对象状态的方法(如各种
get
、set
方法) - 保证类不会被扩展。(禁止继承)
- 声明所有的域都是 final 的
- 声明所有的域都是私有的
- 确保对于任何可变组件的互斥访问
2. 不可变类的例子
下面展示编写一个不可变的复数类的例子。所有的运算函数都不会改变原有对象的值,而是将运算结果以新值返回。
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double rm) {
this.re = re;
this.im = rm;
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}
public Complex dividedBy(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex(
(re * c.re + im * c.im) / tmp,
(re * c.re - im * c.im) / tmp
);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Complex complex = (Complex) o;
return Double.compare(complex.re, re) == 0 && Double.compare(complex.im, im) == 0;
}
@Override
public int hashCode() {
return Objects.hash(re, im);
}
@Override
public String toString() {
return String.format("(%f%+fi)", re, im);
}
public static void main(String[] args) {
Complex c1 = new Complex(1.2, 2.2);
Complex c2 = new Complex(1.6, 1.7);
System.out.println(c1.times(c2));
}
}
3. 不可变类的优缺点
- 优点
- 不可变对象比较简单:不可变对象可以只有一种状态,即被创建时的状态。
- 不可变对象本质上是线程安全的,不要同步:不可变对象可以被自由共享,进而可以创建一些静态的常用对象,避免垃圾回收带来的影响
- 不可变对象可以共享它们内部的信息:
- 不可变对象为其他对象提供大量的构件
- 不可变对象无偿地提供了失败的原子性
- 缺点:
- 对于每个不同值都需要一个单独的对象
4. 总结
-
尽可能少地编写set方法。除非有很好的理由要让类成为可变的类,否则它就应该是不可变的。-
-
如果类不能做成不可变的,应该尽可能限制其可变性。
-
每个域尽可能是
private final
的 -
构造器应该创建完全初始化的对象,并建立起所有的约束关系。不要在构造器或静态工厂之外再提供公有的初始化方法。