Immutable不可变对象设计模式

什么是Immutable

Immutable意为不可改变的,如果一个对象定义成了不可变的(即Immutable Object),就意味着该对象在初始化完成之后它的属性是不能够被修改的。在并发编程中我们可以将对象设计成Immutable Object从而不用加锁实现线程安全,因为不可变对象一定是线程安全的,同时由于不需要用一些锁机制等保证内存一致性问题也减少了同步开销。
谈到Immutable Object会让很多Javaer联想到Java语言中的final关键字,final关键字可以修饰属性、方法和类。final的作用为

final 修饰的 class 代表该类不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。

那是不是被final修饰的对象就可以认为是Immutable Object呢?当然不是。请看下面的例子

final List<String> strList = new ArrayList<String>();
strList.add("ONE");  //ok
strList.add("TWO");  //ok
List<String> unmodifiableStrList = Collections.unmodifiableList(strList);
unmodifiableStrList.add("THREE"); //throw UnsupportedOperationException

这个例子中strList声明成final,只能说明strList变量指向的地址空间不能改变但是该地址空间指向的内容是可以修改的。

如何定义Immutable Object

Java 语言目前还没有原生的不可变对象的支持,但在Java™ Tutorials中给出的如何定义一个不可变对象的方法。

  1. 类中的属性不提供"setter"方法;
  2. 类中所有的属性声明成private和final类型;
  3. 类也声明成final的,以防止类被继承;
  4. 如果有属性是引用类型的,也要防止引用类型的属性被调用方修改了,如通过构造器初始化所有成员,尤其是引用对象要进行深拷贝(deep copy,符合copy-on-write 原则);
  5. 如果确实需要实现 getter 方法,或者其他可能会返回内部状态的方法,也要深拷贝,创建私有的 copy。

针对上面提到的第4点进行一下说明,如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。如:

public final class ImmutableDemo {  
    private final int[] myArray;  
    public ImmutableDemo(int[] array) {  
        this.myArray = array; // wrong  , 调用方可以对传入的array进行修改
    }  
}

下面是一个Immutable Object的例子,该对象可以用在并发环境下而没有线程安全问题。

final public class Person {
    private final String name;
    private final String address;

    public Person(String name,String address){
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

再来看看上面示例中使用到的Collections.unmodifiableList()方法是如何将一个可变的对象变成不可变对象

public static <T> List<T> unmodifiableList(List<? extends T> list) {
    // 这里传入的list是ArrayList,他是RandomAccess的实例
    return (list instanceof RandomAccess ?
            new UnmodifiableRandomAccessList<>(list) :
            new UnmodifiableList<>(list));
}

//UnmodifiableRandomAccessList对象是Collections的内部类
static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E>
        implements RandomAccess
{
    UnmodifiableRandomAccessList(List<? extends E> list) {
        super(list);
    }
    ...
}

UnmodifiableListUnmodifiableRandomAccessList的父类,它们两个都是Collections的静态内部类。

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
        implements List<E> {
    
    final List<? extends E> list;

    UnmodifiableList(List<? extends E> list) {
        super(list);
        this.list = list;
    }

    public E get(int index) {return list.get(index);}
    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
    ...
}

List<String> unmodifiableStrList = Collections.unmodifiableList(strList);语句执行之后unmodifiableStrList变量实际指向的类型会变成UnmodifiableList。然后再执行add方法的时候就会抛出UnsupportedOperationException异常。
除了unmodifiableList()方法之外,Collections中还定义了将其他集合修饰成不可变对象的方法。

  • Collections.unmodifiableCollection()
  • Collections.unmodifiableMap()
  • Collections.unmodifiableSortedMap()
  • Collections.unmodifiableSet()
  • Collections.unmodifiableSortedSet()

其基本实现思路和unmodifiableList()方法是一样的,另外Arrays.asList("ONE","TWO");返回的List对象也是不可变的。

String对象的不可变性

String是典型的 Immutable 类,被声明成为 final class,它的属性基本都是 final 的。由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。下面是String源码的部分摘录,可以看到它是符合Immutable Object的定义规则的。

// 类被声明成final的,防止被继承
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    /** 属性也被声明成final的,且没有提供setter方法 */
    private final char value[];
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
    
    /** 进行深拷贝来构造引用类型的成员变量 */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

    /** 通过深拷贝对外提供属性的copy版本 */
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
    //
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
    ....
}

参考连接
A Strategy for Defining Immutable Objects
JAVA不可变类(immutable)机制与String的不可变性

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,874评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,281评论 19 139
  • Java 语言支持的类型分为两类:基本类型和引用类型。整型(byte 1, short 2, int 4, lon...
    xiaogmail阅读 1,392评论 0 10
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,158评论 0 62
  • 何德恺,90后人气小太阳,微信大V,豆瓣红人,简书高人气作者...二十多岁的你在做什么? 每个人在二十多岁的时候都...
    我是绾绾阅读 258评论 0 0