什么是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中给出的如何定义一个不可变对象的方法。
- 类中的属性不提供"setter"方法;
- 类中所有的属性声明成private和final类型;
- 类也声明成final的,以防止类被继承;
- 如果有属性是引用类型的,也要防止引用类型的属性被调用方修改了,如通过构造器初始化所有成员,尤其是引用对象要进行深拷贝(deep copy,符合copy-on-write 原则);
- 如果确实需要实现 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);
}
...
}
UnmodifiableList
是UnmodifiableRandomAccessList
的父类,它们两个都是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的不可变性