本文整理自《Effective Java》一书。
不可变类,并在对象的整个生命周期(lifetime)内保持不变。Java平台类库中包含许多不可变的类,其中有String、基本类型的包装类、BigInteger、BigDecimal。
为了使类成为不可变,要遵循下面五条规则:
1. 不要提供任务会修改对象状态的方法。
2. 保证类不会被扩展。常见做法final Class、 private constructor并添加公有的静态工厂(static factory)来代替公有的构造器,如 public static Complex valueOf(){ return new Complex();}。
3. 使所有的域都是final的。final。
4. 使所有的域都成为私有的。private 。
5. 确保对于任何可变组件的互斥访问。采用函数的(functional)做法,防止this引用逸出,如String中的做法:
public static String copyValueOf(char data[]) {
return new String(data);
}
不可变对象本质上是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时,它们不会遭到破坏,所以,不可变对象可以被自由的共享。不可变类应该充分利用这种优势,鼓励客户端尽可能地重用现有的实例,对于频繁用到的值,为它们提供公有的static final 常量,如 Integer 提供的 public static final intMIN_VALUE =-231 。
不可变类的真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。创建这种对象的代价可能很高,特别是对于大型对象的情形。所以,最好的办法是提供一个公有的可变配套类。在Java平台类库中,这种方法的主要例子是String类,以及它的可变配套类StringBuilder(和基本上已经废弃的StringBuffer)。
当BigInteger和BigDecimal刚被编写出来的时候,对于“不可变的类必须为final的”还没有得到广泛地理解,所以它们的所有方法都可能被覆盖,遗憾的是,为了保持向后兼容,这个问题一直无法得以修正。
public class BigDecimal
extends Number
implements Comparable<BigDecimal>
如果你在编写一个类,它的安全性依赖于BigInteger或者BigDecimal参数的不可变性,就必须进行检查,以确定这个参数是否为“真正的”BigInteger或者BigDecimal,而不是不可信任子类的实例。如果是后者的话,就必须在假设它可能是可变的前提下对它进行保护性拷贝:
public static BigInteger safeInstance(BigInteger val){
if(val.getClass()!= BigInteger.class){
return new BigInteger(val.toByteArray());
}
return val;
}
实际中,上述规则规定不可变类的所有域都必须是final的。这个要求为了提高性能可以有所放松,只要没有一个方法能够对对象的状态产生外部可见的改变。如String类的
/** Cache the hash code for the string */private int hash; 采用延迟初始化来缓存一些开销昂贵的计算结果到这些域中。
除非有令人信服的理由要使域变成是非final的,否则要使每个域都是final的。关于final域:"When final is used withobject references rather than primitives, the meaning can be confusing. With aprimitive, final makes the value a constant, but with an object reference,final makes the reference a constant. Once the reference is initialized to anobject, it can nerver be changed to point to another object. However, theobject itself can be modified." From《Thinking in Java》。final修饰域时,只保证引用的不可变,而不保证引用指向的对象不可变,除非这个对象本身是不可变。请一定确保公有类的公有static final域所引用的对象都是不可变的。