[译] 不可变类有什么优势,应该如何创建?

不可变类是指对象状态在创建时就确定,之后无法改变的类。其中状态是指类的成员变量,包括原生类型和引用类型。

不可变类的优势

  • 方便构造、测试和使用
  • 线程安全,没有同步问题
  • 不需要拷贝构造方法
  • 不需要实现Clone方法
  • 可以缓存类的返回值,允许hashCode使用惰性初始化方式
  • 不需要防御式复制
  • 适合用作Map的key和Set的元素(因为集合里这些对象的状态不能改变)
  • 类一旦构造完成就是不变式,不需要再次检查
  • 总是“failure atomicity”(原子性失败):如果一个不可变对象抛出异常,它从不会保留一个烦人的或者不确定的状态

如何创建不可变类

关于创建不可变对象,Oracle也有说明:详细介绍
主要是以下几点:

  1. Don't provide "setter" methods — methods that modify fields or objects referred to by fields
  2. Make all fields final and private
  3. 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.
  4. 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.
  1. 不提供setter方法,避免对象的域被修改
  2. 将所有的域都设置为private final
  3. 不允许子类覆盖父类方法。最简单的方法是将class设为final。更好点的方式是将构造方法设为private,同时通过工厂方法来创建实例
  4. 如果域包含其他可变类的对象,也要禁止这些对象被修改:
  • 不提供修改可变对象的方法
  • 不要共享指向可变对象的引用。不要存储那些传进构造方法的外部可变对象的引用;如果需要,创建拷贝,保存指向拷贝的引用。类似的,在创建方法返回值时,避免返回原始的内部可变对象,而是返回可变对象的拷贝。

根据以上规则可以实现一个不可变类

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

从输出结果可以看出,用实例内部成员的引用来改变实例的值是无效的,这个类是不可变类。

原文链接

你可能感兴趣的:([译] 不可变类有什么优势,应该如何创建?)