1. 什么是不可变类
不可变类是指一旦通过构造器初始化一个类的对象状态后,在这个对象的生命周期内,它的内部状态是不能改变的。例如java中的string、integer对象等等。
2. 为什么要设计不可变了,类不可变的好处
(1) 不可变类是线程安全的。
因为不可变类的对象的状态在构造器中一次设置完成后,在这个对象的整个生命周期内都不能改变它的内部状态了,所以在不同线程中可以安全的共享不可变类的对象(因为不同线程都不能修改不可变类的状态,所以就不需要同步了)。
(2) 不可变对象可以安全的作为散列的键值
每个对象都是独一无二的,不可变类的对象构造完成后,域是不能变得。所以hashcode就固定下来了。
以下例子可以说明不可变对象在散列存储结构中的使用。
可变对象:
public class StringHolder {
private String s ;
publicStringHolder(String s){
this.s = s;
}
@Override
public boolean equals(Object obj){
if(obj == this)
return true;
if(!(obj instanceofStringHolder))
return false;
StringHolderh = (StringHolder) obj;
return h.s.equals(s);
}
public void setString(Strings){
this.s = s;
}
public String toString(){
return s;
}
@Override
public int hashCode() {
return (s != null) ? s.hashCode() : 0;
}
把这个可变类的对象,放入HashSet中,然后再去改变可变对象域的值。会使得HashSet的行为不确定。
StringHolder h = new StringHolder("hua");
System.out.println(h.hashCode());
Set<StringHolder> set = new HashSet<StringHolder>();
set.add(h);
h.setString("Hua");
System.out.println(h.hashCode());
System.out.println(set.contains(h));--false,明明在HashSet中放了h
但是返回false
System.out.println(set.size());--返回1
System.out.println(set.iterator().next());
3. 设计不可变类的步骤
(1) 用final来修饰所有的域
(2) 不要提供可以修改域的方法(modify method)
(3) 类用final修饰,也就是说不可以子类化
这是为了防止子类破坏不变性约束
(4) 使得所有的域都是private的。
(5) 保证对可变域的访问都是互斥的
如果不可变中有有域指向的是一个可变的对象,那么必须确保client端不能得到可变对象的引用,阻止client端修改可变对象。
如何保证:
使可变域private
除了在构造器中对可变域初始化后,不要再其他地方修改可变域的值
在方法中不要暴露可变域的引用
对可变域要进行保护性拷贝,只有这样才能真正使得一个类的对象在生命周期中不可变。
保护性拷贝
1. 不可变类中如果有可变类的引用的话,需要做保护性拷贝
public final class Period {
private final Date start;
private final Date end;
public Period(final Date start,final Date end) {
if(start.compareTo(end) > 0)
throw new IllegalArgumentException(start + "after" + end);
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
public void checkV() {
if(start.compareTo(end) > 0)
throw new IllegalArgumentException(start + "after" + end);
}
Period类是不可变类吗?粗看貌似是不变类,约束在构造器中定义了。
但是我们这么用Period却能轻易改变Period类的内部状态。
Date start = new Date();
Date end = new Date();
Period p = new Period(start,end);
start.setMonth(11);
p.checkV();
问题在于Date类的对象是个可变的对象,我们域中的start引用直接指向了与client端new的同一个Date对象,Period不能阻止client端去修改域start指向的对象。
(1)构造器中传入一个可变对象时,我们需要做一些保护性的copy。
public Period(final Date start,final Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(start.compareTo(end) > 0)
throw new IllegalArgumentException(start + "after" + end);
}
(2) 方法中返回对象的时候也要做保护性copy;反正不变类中的域和client端使用的对象,不能是同一个对象,也就说说不变类中的可变对象不能对client端逸出。
Date start = new Date();
Date end = new Date();
Period p = new Period(start,end);
P.getStartDate().setMonth(2013);
p.checkV();
修正getStartDate():
public Date getStart() {
return newDate(start.getTime());
}
不可变类中有可变的数组
1. 不用使用clone方法来copy数组,不好
2. 用java.util.Arrays中copyOf复制一个新的数组。