这篇东西我写了好长时间,因为我把Thinking in Java, 3rd Ed中有关内容仔细翻了个遍。E文版的看起来速度虽然慢,却很引人思考。
Java平台中有些类被称为Immutable Class,因为这些类中的方法不能改变当前对象的值。
举个例子,java.lang.String是个Immutable Class,如果我们这样写:
String str = "ZephyrFalcon";
str.substring(0, 6);
那么str的内容是什么呢?没错,还是"ZephyrFalcon"。str.substring(0, 6)返回的确实是"Zephyr",但是它不能改变str指向的对象的的内容,只不过返回一个新的String对象罢了,内容是"Zephyr"。于是,单独执行String.substring()方法没有效果,是没有什么意义的,前面还要有个String reference引用它才行。
类似的,所有基本类型的Wrapper Class也全部是Immutable Class。
Immutable Class的特殊性质会在我们进行Object Cloning的时候发挥特殊效果,呵呵。这我们最后再说。
Object Cloning的概念很简单:复制一个对象。注意,是对象,也就是实例而不是类。All argument passing in Java is performed by passing references(Thinking in Java, 3rd Ed),除了primitives。另外,只有reference有作用域问题,object没有。当我们需要保持原有对象,希望创建它的一个副本的时候(比如创建一个对象的local copy),cloning就派上用场了。
java.lang.Object类具有clone()这个方法,并且是protected权限(public > protected > default > private)。于是所有的Java类全都有clone()这个方法,默认情况是protected。这个protected权限在这里很tricky,它意味着两件事:
1.默认情况下程序员无法对某个类简单的使用clone(),这是由于权限问题不能access。(简单使用指并非继承这个类。)
2.我们无法通过对一个类的base class的reference来调用这个类的clone()。虽然有时候这很有用。比如当我们想要多态的(polymorphically)克隆一大批对象的时候。即这些对象是不同的类型,但是都是Object。我们可以用Object引用去refer它们,但是无法通过这些引用的clone()来复制这些对象。哦,丢人了...实际上这还是因为权限access问题...和上一个一样,我的我的。
当然啦,如果你在一个没有override clone()方法的类里调用这个类本身的clone(),也就是继承的那个,当然是可以的。Object.clone()的行为非常神奇,它会对derived class object进行bitwise duplication,于是我们明明调用的是base class的clone()却能复制出derived class object。
看来Java里的克隆比Heroes III里面的克隆魔法复杂多了!
结论就很自然了,如果我们不override clone()方法,并把其权限变成public的话,其他的类就无法使用它了(不明白自己翻Tutorial/Nutshell去)。当然了,在这个我们自己实现的clone()里,要调用super.clone()。
一旦某个类用上面提到的方法override了clone()方法,它的子类将自动拥有相应的clone自己的能力。如下:
You’ll probably want to override clone() in any further derived classes; otherwise, your (now public) clone() will be used, and that might not do the right thing (although, since Object.clone() makes a copy of the actual object, it might). The protected trick works only once: the first time you inherit from a class that has no cloneability and you want to make a class that’s cloneable. In any classes inherited from your class, the clone() method is available since it’s not possible in Java to reduce the access of a method during derivation. That is, once a class is cloneable, everything derived from it is cloneable unless you use provided mechanisms (described later) to “turn off” cloning.
另外,java.lang.Cloneable提供了克隆能力的标记接口。这个接口没有任何方法,对,和java.io.Serializable一样,它仅仅是个标记接口(tagging interface),标志着此类可以克隆。如果我们调用一个类的clone()方法,而这个类没有实现此接口,则会在运行时抛出java.lang.CloneNotSupportedException异常。
First of all, for clone( ) to be accessible, you must make it public. Second, for the initial part of your clone( ) operation, you should call the base-class version of clone( ). The clone( ) that’s being called here is the one that’s predefined inside Object, and you can call it because it’s protected and thereby accessible in derived classes.
Object.clone( ) figures out how big the object is, creates enough memory for a new one, and copies all the bits from the old to the new. This is called a bitwise copy, and is typically what you’d expect a clone( ) method to do. But before Object.clone( ) performs its operations, it first checks to see if a class is Cloneable—that is, whether it implements the Cloneable interface. If it doesn’t, Object.clone( ) throws a CloneNotSupportedException to indicate that you can’t clone it. Thus, you’ve got to surround your call to super.clone( ) with a try block to catch an exception that should never happen (because you’ve implemented the Cloneable interface).
Whatever you do, the first part of the cloning process should normally be a call to super.clone( ). This establishes the groundwork for the cloning operation by making an exact duplicate. At this point you can perform other operations necessary to complete the cloning.
关于shadow copy和deep copy:
如果某对象的某成员域是引用,那么此对象被clone以后该引用也被clone,仍然指向原来它所指的对象。这称为shadow copy。举个最简单的例子就是,一个容器/集合类,例如java.util.Hashtable被clone以后,它里面存放的引用仍然指向原来的对象。当原来的Hashtable中的对象改变时,新的Hashtable副本中的对象也会改变,原因是它们实际上保存的对象不是local copy,而是引用。
如果我们希望一个对象被clone后,它的成员域引用原来对象新的副本,我们只能在此对象的clone()方法里加入语句来实现。具体来说就是,对于希望引用新副本的成员域,把它原来引用的对象克隆,再让这个成员域引用这个新副本。这称为deep copy。遗憾的是deep copy并不总能成功,因为很多对象是不能克隆的。比如Java标准库里面大多数类都没有实现Cloneable接口。这是由于历史原因。Java语言发明之初被用在机顶盒等设备上,而不是Internet上。这时候让所有的对象都具有Clone能力是很合理的,于是clone()方法被放在java.lang.Object里,而且是public的。后来Java在Internet上流行起来,安全性的重要日益凸现。很多安全相关的对象不能被随意复制。于是clone()变成了protected,还多了Cloneable接口,使类在实现的时候就能决定是否要拥有clone能力。
关于deep copy还有个问题:
There’s a problem you’ll encounter when trying to deep copy a composed object. You must assume that the clone( ) method in the member objects will in turn perform a deep copy on their references, and so on. This is quite a commitment. It effectively means that for a deep copy to work, you must either control all of the code in all of the classes, or at least have enough knowledge about all of the classes involved in the deep copy to know that they are performing their own deep copy correctly.
另外,出于性能上的考虑,我们通常不用object serialization/deserialization代替deep copy。
最后的一点是在clone时Immutable Class表现出来的特殊性。举例来说,我们要clone一个java.util.ArrayList,这个ArrayList里面装的是String,注意这是Immutable Class。clone过后新的ArrayList副本里面元素指向的还是原来那些String,没错。唯一的不同在于,对副本中的String进行操作不会影响到原来的ArrayList中的String。看起来好像是deep copy一样,实际上只是由于副本中的String被操作时由于Immutable Class的性质返回了新的String对象而已。很容易想清楚,不是吗?
联系客服