目录
多线程学习系列 - 1 - Single Threaded Execution Pattern
多线程学习系列 - 2 - Immutable Pattern
先看看什么算是Immutable
immutable [i'mju:təbl](21世纪大英汉词典) adj.永远不变的;不可改变的;永恒的;无变化的
能够保证实例状态绝对不会改变的类,我们认为是immutable的
最常用的String就是immutable类
考虑下面Person类,看它是如何成为一个immutable类
它有属性String name和String address
public final 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; } public String toString(){ return "Person [name:" + name + "\t address:" + address + "]"; } }
类很简单,只有两个字段几个方法
首先Person非常凶猛,上场先挥刀自宫,把自己声明成final绝后,所以也就没有子类能修改父类属性的可能
然后把两个属性都声明为private,也就是说对外不可见,并且设置为final也禁止了set方法的再次赋值
至此Person功德圆满,成为了immutable类
ps:如果你使用反射,很遗憾,反射可以改变name和address的值,这里我们不考虑恶意改变,所以去除这种情况
测试代码如下
public static void main(String[] args) { Person p = new Person("Alice", "Alaska"); System.out.println(p);; Class<Person> clazz = Person.class; try { Field feild = clazz.getDeclaredField("name"); feild.setAccessible(true); feild.set(p, "Bobby"); System.out.println(p); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
打印结果
Person [name:Alice address:Alaska]
Person [name:Bobby address:Alaska]
Immutable Pattern的参与者
一个字段值无法更改的类,也没有任何用来更改字段值的方法。当Immutable参与者的实例建立后,状态就完全不再变化
Immutable Pattern适用性
1.当实例产生后,状态不需要再改变时(原文为状态不再变化时,我觉得改成不需要再改变时会更好)
其实只将字段定义为private final并且没有set方法,也还是有可能是mutable类的,后面会给出例子,上面的Person确实是immutable的
2.实例需要共享,而且访问很频繁时
使用immutable类可以省去synchronized,性能会节省不少且不丧失安全性
标准类链接库里使用到的Immutable Pattern
下面只简单列举一些
java.lang.String
java.awt.Color
和基本类型的包装类
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Short
java.lang.Void
习题2.5
一个人设计的line,看看这个line是否为不可变类
public class Line { private final Point startPoint; private final Point endPoint; public Line(int startx, int starty, int endx, int endy) { this.startPoint = new Point(startx, starty); this.endPoint = new Point(endx, endy); } public Line(Point startPoint, Point endPoint) { this.startPoint = startPoint; this.endPoint = endPoint; } public int getStartX() { return startPoint.getX(); } public int getStartY() { return startPoint.getY(); } public int getEndX() { return endPoint.getX(); } public int getEndY() { return endPoint.getY(); } public String toString() { return "[ Line: " + startPoint + "-" + endPoint + " ]"; } }
Point类
public class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public String toString() { return "(" + x + "," + y + ")"; } }
这里我们先不考虑继承的问题,因为人家设计完了,没有其余的类了
那么看看这个line,所有字段都为private final,方法都是get并且返回int和String
我们来看看这个构造函数
public Line(Point startPoint, Point endPoint) { this.startPoint = startPoint; this.endPoint = endPoint; }
问题出在这个Point上
很容易看出,Point是可变的,line赋值之后Point改变了会怎样?很简单,line也被改变了
下面的代码证实了这点
public static void main(String[] args) { Point s = new Point(1, 2); Point e = new Point(3, 4); Line line = new Line(s, e); System.out.println(line); e.x = 5; System.out.println(line); }
打印结果
[ Line: (1,2)-(3,4) ]
[ Line: (1,2)-(5,4) ]
成对出现的mutable与immutable
如果有这样一个对象,它的一部分属性是不可变的另一部分是可变的,可以明显的区分出来,那么我们可以考虑将它拆分为两个类
一个为immutable另一个为mutable
比如String和StringBuffer
我们在java源码中可以看到
现在来看看下面两个成对出现的类,他们是否有安全问题
习题2.6
public final class ImmutablePerson { private final String name; private final String address; public ImmutablePerson(String name, String address) { this.name = name; this.address = address; } public ImmutablePerson(MutablePerson person) { this.name = person.getName(); this.address = person.getAddress(); } public MutablePerson getMutablePerson() { return new MutablePerson(this); } public String getName() { return name; } public String getAddress() { return address; } public String toString() { return "[ ImmutablePerson: " + name + ", " + address + " ]"; } }
public final class MutablePerson { private String name; private String address; public MutablePerson(String name, String address) { this.name = name; this.address = address; } public MutablePerson(ImmutablePerson person) { this.name = person.getName(); this.address = person.getAddress(); } public synchronized void setPerson(String newName, String newAddress) { name = newName; address = newAddress; } public synchronized ImmutablePerson getImmutablePerson() { return new ImmutablePerson(this); } String getName() { // Called only by ImmutablePerson return name; } String getAddress() { // Called only by ImmutablePerson return address; } public synchronized String toString() { return "[ MutablePerson: " + name + ", " + address + " ]"; } }
题目很简单,和2.5基本一样
public ImmutablePerson(MutablePerson person) { this.name = person.getName(); this.address = person.getAddress(); }
解释也和2.5一样,那么如何修改
在执行这个方法的时候,MutablePerson可能会改变,如何被改变,可以通过setPerson,setPerson使用了synchronized关键字,意为获得this的锁,那么我们只需也获取这个对象的锁,就可以保证setPerson和public ImmutablePerson(MutablePerson person)不会交叉执行了
代码如下
public ImmutablePerson(MutablePerson person) { synchronized (person) { this.name = person.getName(); this.address = person.getAddress(); } }
这节内容较少,所以就不画图了
转贴请保留以下链接
本人blog地址