如果一个对象构造之后,它的状态不会改变,那么,这个对象是不可变的。最大化的使用不可变对象在简单、可靠的编程中被广泛的接受。
不可变对象在并发编程中特别有用,由于它们不会改变状态,它们在线程交互被破坏或者在状态不一致性中可见。
很多程序员反对使用不可变对象,因为他们担心创建新对象的代价比更新一个对象来的高,创建对象的影响经常被高估了,并且抵消不可变对象关联的效率,这包括减少垃圾回收开销、保护可变对象以免被破坏而需要的代码清除。
接下来的章节使用一个可变类对象和一个继承它的不可变实例。为了实现这样,它们给这种转换一个统一的规则并演示不可变对象的有点。
一个同步类例子
类SynchronizedRGB,定义一些代表颜色的对象,每个对象代表一种颜色,由三个代表基本颜色值的整数和一个颜色的名字组成。
public class SynchronizedRGB{
private int red;
private int green;
private int blue;
private String name;
private void check(int red,int green,int blue){
for(red<0||red>255||green<0||green>255||blue<0||blue>255){
throw new IllegalArgumentException();
}
}
public SynchronizedRGB(int red,int green,int blue,String name){
check(red,green,blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
public void set(int red,int green,int blue,String name){
check(red,green,blue);
synchronized(this){
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
}
public synchronized int getRGB(){
return((red<<16)|(green<<8)|blue);
}
public synchronized String getName(){
return name;
}
public synchronized void invert(){
red = 255 - red;
green = 255 - green;
blue = 255 - blue;
name = "Inverse of " + name;
}
}
SynchronizedRGB必须小心的使用以免不一致性状态可见性,假设,如下例子,一个线程执行如下代码:
synchronized(color){
int myColorInt = color.getRGB();
String myColorName = color.getName();
}
这种类型的不一致性仅仅在可变对象才会出现,对不可变对象则没有这种问题。
定义不可变对象的策略
接下来定义一个简单创建不可变对象的策略,不是所有标记为不可变类都遵循这些规则,这并不是创建这些类的作者粗心,他们有好的理由相信他们类对象在构建之后不会再改变,然而,这些策略需要精细的分析而不合适于初学者。
- 不提供setter方法 —— 修改属性的方法或者引用属性对象
- 将所有的属性设置成final和private
- 不允许子类重写方法。最简单的放是将类声明为final。另外一个复杂的方式是将构造方法设置为私有并且在工厂方法中构造实例对象。
如果实例属性包含可变对象的引用,也不允许这些对象发生变化。
- 不提供修改可变对象的方法
- 不要共享可变对象的引用, 不要在外部保留引用,不要将可变对象传给构造方法;如果需要的话,创建副本,保存副本对象的引用。简单的,创建内部可变对象的副本,必要时,避免返回你方法的原始部分。
将这个策略用于SynchronizedRGB,结果如下:
- 这个类中有两个setter方法,第一个set方法,随意转换对象,不在一个不可变类的版本;第二个invert方法,能够适用于通过创建一个新的对象替代修改存在的对象。
- 所有的属性已经设置为私有,他们进一步被限制为final
- 类本身声明为final
- 仅仅有一个属性是对线的引用,并且对象本身是不可变的,因此,没有必要保证对象状态的变化。
通过以上修改,我们有了新的类ImmutableRGB:
final public class ImmutableRGB{
final private int red;
final private int green;
final private int blue;
final private String name;
priavte void check(int red,int green,int blue){
if(red<0||red>255||green<0||green>255||blue<0||blue>255){
throw new IllegalArgumentException();
}
}
public ImmutableRGB(int red,int green,int blue,String name){
check(red,green,blue);
this.red = red;
this.green = green;
this.blue = blue;
}
public int getRGB(){
return((red<<16)|(green<<8)|blue);
}
public ImmutableRGB invert(){
return new ImmutableRGB(255-red,255-green,255-blue,"Inverse of " + name);
}
}