不可变类是指对象状态在创建时就确定,之后无法改变的类。其中状态是指类的成员变量,包括原生类型和引用类型。
不可变类的优势
- 方便构造、测试和使用
- 线程安全,没有同步问题
- 不需要拷贝构造方法
- 不需要实现Clone方法
- 可以缓存类的返回值,允许hashCode使用惰性初始化方式
- 不需要防御式复制
- 适合用作Map的key和Set的元素(因为集合里这些对象的状态不能改变)
- 类一旦构造完成就是不变式,不需要再次检查
- 总是“failure atomicity”(原子性失败):如果一个不可变对象抛出异常,它从不会保留一个烦人的或者不确定的状态
如何创建不可变类
关于创建不可变对象,Oracle也有说明:详细介绍
主要是以下几点:
- Don't provide "setter" methods — methods that modify fields or objects referred to by fields
- Make all fields final and private
- Don't allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.
- If the instance fields include references to mutable objects, don't allow those objects to be changed:
- Don't provide methods that modify the mutable objects.
- Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.
- 不提供setter方法,避免对象的域被修改
- 将所有的域都设置为private final
- 不允许子类覆盖父类方法。最简单的方法是将class设为final。更好点的方式是将构造方法设为private,同时通过工厂方法来创建实例
- 如果域包含其他可变类的对象,也要禁止这些对象被修改:
- 不提供修改可变对象的方法
- 不要共享指向可变对象的引用。不要存储那些传进构造方法的外部可变对象的引用;如果需要,创建拷贝,保存指向拷贝的引用。类似的,在创建方法返回值时,避免返回原始的内部可变对象,而是返回可变对象的拷贝。
根据以上规则可以实现一个不可变类
import java.util.Date;
/**
* 注意实例的变量本身可能是不可变的,也可能是可变的
* 对于所有可变的成员变量,返回时需要复制一份新的
* 不可变的成员变量不用做特殊处理
* */
public final class ImmutableClass
{
/**
* Integer类是不可变的,因为它没有提供任何setter方法来改变值
* */
private final Integer immutableField1;
/**
* String类是不可变的,它也没有提供任何setter方法来改变值
* */
private final String immutableField2;
/**
* Date类是可变的,它提供了改变日期或时间的setter方法
* */
private final Date mutableField;
// 将构造方法声明为private,确保不会有意外情况构造这个类
private ImmutableClass(Integer fld1, String fld2, Date date)
{
this.immutableField1 = fld1;
this.immutableField2 = fld2;
this.mutableField = new Date(date.getTime());
}
// 工厂方法将创建对象的逻辑封装在一个地方
public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
{
return new ImmutableClass(fld1, fld2, date);
}
// 不提供setter方法
/**
* Integer类是不可变的,可以直接返回成员变量的实例
* */
public Integer getImmutableField1() {
return immutableField1;
}
/**
* String类是不可变的,可以直接返回成员变量的实例
* */
public String getImmutableField2() {
return immutableField2;
}
/**
* Date类是可变的,需要注意一下
* 不要返回原始成员变量的引用
* 创建一个新的Date对象,内容和成员变量一样
* */
public Date getMutableField() {
return new Date(mutableField.getTime());
}
@Override
public String toString() {
return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
}
}
测试类
class TestMain
{
public static void main(String[] args)
{
ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
System.out.println(im);
tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
System.out.println(im);
}
private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
{
immutableField1 = 10000;
immutableField2 = "test changed";
mutableField.setDate(10);
}
}
Output: (content is unchanged)
100 - test - Tue Oct 30 21:34:08 IST 2012
100 - test - Tue Oct 30 21:34:08 IST 2012
从输出结果可以看出,用实例内部成员的引用来改变实例的值是无效的,这个类是不可变类。