12.2 制作本地副本
12.2 制作本地副本
①:在
class Int { private int i; public Int(int ii) { i = ii; } public void increment() { i++; } public String toString() { return Integer.toString(i); } }
public class Cloning { public static void main(String[] args) { Vector v = new Vector(); for(int i = 0; i < 10; i++ ) v.addElement(new Int(i)); System.out.println(“v: " + v); Vector v2 = (Vector)v.clone(); // Increment all v2’s elements: for(Enumeration e = v2.elements(); e.hasMoreElements(); ) ((Int)e.nextElement()).increment(); // See if it changed v’s elements: System.out.println(“v: " + v); } } ///:~
一般来说,由于不敢保证
②
-
使用
protected 时的技巧 为避免我们创建的每个类都默认具有克隆能力,clone() 方法在基础类Object 里得到了“保留”(设为protected ) 。这样造成的后果就是:对那些简单地使用一下这个类的客户程序员来说,他们不会默认地拥有这个方法;其次,我们不能利用指向基础类的一个指针来调用clone() (尽管那样做在某些情况下特别有用,比如用多态性的方式克隆一系列对象) 。在编译期的时候,这实际是通知我们对象不可克隆的一种方式——而且最奇怪的是,Java 库中的大多数类都不能克隆。因此,假如我们执行下述代码: Integer x = new Integer(l); x = x.clone(); 那么在编译期,就有一条讨厌的错误消息弹出,告诉我们不可访问clone() ——因为Integer 并没有覆盖它,而且它对protected 版本来说是默认的) 。 但是,假若我们是在一个从Object 衍生出来的类中(所有类都是从Object 衍生的) ,就有权调用Object.clone() ,因为它是“protected”,而且我们在一个迭代器中。基础类clone() 提供了一个有用的功能——它进行的是对衍生类对象的真正“按位”复制,所以相当于标准的克隆行动。然而,我们随后需要将自己的克隆操作设为public ,否则无法访问。总之,克隆时要注意的两个关键问题是:几乎肯定要调用super.clone() ,以及注意将克隆设为public 。 有时还想在更深层的衍生类中覆盖clone() ,否则就直接使用我们的clone() (现在已成为public ) ,而那并不一定是我们所希望的(然而,由于Object.clone() 已制作了实际对象的一个副本,所以也有可能允许这种情况) 。protected 的技巧在这里只能用一次:首次从一个不具备克隆能力的类继承,而且想使一个类变成“能够克隆”。而在从我们的类继承的任何场合,clone() 方法都是可以使用的,因为Java 不可能在衍生之后反而缩小方法的访问范围。换言之,一旦对象变得可以克隆,从它衍生的任何东西都是能够克隆的,除非使用特殊的机制(后面讨论)令其“关闭”克隆能力。 -
实现
Cloneable 接口 为使一个对象的克隆能力功成圆满,还需要做另一件事情:实现Cloneable 接口。这个接口使人稍觉奇怪,因为它是空的! interface Cloneable {} 之所以要实现这个空接口,显然不是因为我们准备上溯造型成一个Cloneable ,以及调用它的某个方法。有些人认为在这里使用接口属于一种“欺骗”行为,因为它使用的特性打的是别的主意,而非原来的意思。Cloneable interface 的实现扮演了一个标记的角色,封装到类的类型中。 两方面的原因促成了Cloneable interface 的存在。首先,可能有一个上溯造型指针指向一个基础类型,而且不知道它是否真的能克隆那个对象。在这种情况下,可用instanceof 关键字(第11 章有介绍)调查指针是否确实同一个能克隆的对象连接: if(myHandle instanceof Cloneable) // … 第二个原因是考虑到我们可能不愿所有对象类型都能克隆。所以Object.clone() 会验证一个类是否真的是实现了Cloneable 接口。若答案是否定的,则“掷”出一个CloneNotSupportedException 异常。所以在一般情况下,我们必须将“implement Cloneable”作为对克隆能力提供支持的一部分。12.2.4 成功的克隆 理解了实现clone() 方法背后的所有细节后,便可创建出能方便复制的类,以便提供了一个本地副本: //: LocalCopy.java // Creating local copies with clone() import java.util.*;
class MyObject implements Cloneable { int i; MyObject(int ii) { i = ii; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(“MyObject can’t clone”); } return o; } public String toString() { return Integer.toString(i); } }
public class LocalCopy { static MyObject g(MyObject v) { // Passing a handle, modifies outside object: v.i++; return v; } static MyObject f(MyObject v) { v = (MyObject)v.clone(); // Local copy v.i++; return v; } public static void main(String[] args) { MyObject a = new MyObject(11); MyObject b = g(a); // Testing handle equivalence, // not object equivalence: if(a == b) System.out.println(“a == b”); else System.out.println(“a != b”); System.out.println(“a = " + a); System.out.println(“b = " + b); MyObject c = new MyObject(47); MyObject d = f(c); if(c == d) System.out.println(“c == d”); else System.out.println(“c != d”); System.out.println(“c = " + c); System.out.println(“d = " + d); } } ///:~
不管怎样,
大家要记住这样一个事实:
public class Snake implements Cloneable { private Snake next; private char c; // Value of i == number of segments Snake(int i, char x) { c = x; if(–i > 0) next = new Snake(i, (char)(x + 1)); } void increment() { c++; if(next != null) next.increment(); } public String toString() { String s = “:” + c; if(next != null) s += next.toString(); return s; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) {} return o; } public static void main(String[] args) { Snake s = new Snake(5, ‘a’); System.out.println(“s = " + s); Snake s2 = (Snake)s.clone(); System.out.println(“s2 = " + s2); s.increment(); System.out.println( “after s.increment, s2 = " + s2); } } ///:~
一条
这意味着只有第一段才是由
class DepthReading implements Cloneable { private double depth; public DepthReading(double depth) { this.depth = depth; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } }
class TemperatureReading implements Cloneable { private long time; private double temperature; public TemperatureReading(double temperature) { time = System.currentTimeMillis(); this.temperature = temperature; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } }
class OceanReading implements Cloneable { private DepthReading depth; private TemperatureReading temperature; public OceanReading(double tdata, double ddata){ temperature = new TemperatureReading(tdata); depth = new DepthReading(ddata); } public Object clone() { OceanReading o = null; try { o = (OceanReading)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } // Must clone handles: o.depth = (DepthReading)o.depth.clone(); o.temperature = (TemperatureReading)o.temperature.clone(); return o; // Upcasts back to Object } }
public class DeepCopy { public static void main(String[] args) { OceanReading reading = new OceanReading(33.9, 100.5); // Now clone it: OceanReading r = (OceanReading)reading.clone(); } } ///:~
class Int2 implements Cloneable { private int i; public Int2(int ii) { i = ii; } public void increment() { i++; } public String toString() { return Integer.toString(i); } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(“Int2 can’t clone”); } return o; } }
// Once it’s cloneable, inheritance // doesn’t remove cloneability: class Int3 extends Int2 { private int j; // Automatically duplicated public Int3(int i) { super(i); } }
public class AddingClone { public static void main(String[] args) { Int2 x = new Int2(10); Int2 x2 = (Int2)x.clone(); x2.increment(); System.out.println( “x = " + x + “, x2 = " + x2); // Anything inherited is also cloneable: Int3 x3 = new Int3(7); x3 = (Int3)x3.clone();
Vector v = new Vector();
for(int i = 0; i < 10; i++ )
v.addElement(new Int2(i));
System.out.println("v: " + v);
Vector v2 = (Vector)v.clone();
// Now clone each element:
for(int i = 0; i < v.size(); i++)
v2.setElementAt(
((Int2)v2.elementAt(i)).clone(), i);
// Increment all v2's elements:
for(Enumeration e = v2.elements();
e.hasMoreElements(); )
((Int2)e.nextElement()).increment();
// See if it changed v's elements:
System.out.println("v: " + v);
System.out.println("v2: " + v2);
} } ///:~
class Thing1 implements Serializable {} class Thing2 implements Serializable { Thing1 o1 = new Thing1(); }
class Thing3 implements Cloneable { public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(“Thing3 can’t clone”); } return o; } }
class Thing4 implements Cloneable { Thing3 o3 = new Thing3(); public Object clone() { Thing4 o = null; try { o = (Thing4)super.clone(); } catch (CloneNotSupportedException e) { System.out.println(“Thing4 can’t clone”); } // Clone the field, too: o.o3 = (Thing3)o3.clone(); return o; } }
public class Compete { static final int SIZE = 5000; public static void main(String[] args) { Thing2[] a = new Thing2[SIZE]; for(int i = 0; i < a.length; i++) a[i] = new Thing2(); Thing4[] b = new Thing4[SIZE]; for(int i = 0; i < b.length; i++) b[i] = new Thing4(); try { long t1 = System.currentTimeMillis(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); for(int i = 0; i < a.length; i++) o.writeObject(a[i]); // Now get copies: ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buf.toByteArray())); Thing2[] c = new Thing2[SIZE]; for(int i = 0; i < c.length; i++) c[i] = (Thing2)in.readObject(); long t2 = System.currentTimeMillis(); System.out.println( “Duplication via serialization: " + (t2 - t1) + " Milliseconds”); // Now try cloning: t1 = System.currentTimeMillis(); Thing4[] d = new Thing4[SIZE]; for(int i = 0; i < d.length; i++) d[i] = (Thing4)b[i].clone(); t2 = System.currentTimeMillis(); System.out.println( “Duplication via cloning: " + (t2 - t1) + " Milliseconds”); } catch(Exception e) { e.printStackTrace(); } } } ///:~
其中,
Duplication via serialization: 3410 Milliseconds Duplication via cloning: 110 Milliseconds
Duplication via serialization: 3520 Milliseconds Duplication via cloning: 110 Milliseconds
除了序列化和克隆之间巨大的时间差异以外,我们也注意到序列化技术的运行结果并不稳定,而克隆每一次花费的时间都是相同的。
class Person {} class Hero extends Person {} class Scientist extends Person implements Cloneable { public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // this should never happen: // It’s Cloneable already! throw new InternalError(); } } } class MadScientist extends Scientist {}
public class HorrorFlick { public static void main(String[] args) { Person p = new Person(); Hero h = new Hero(); Scientist s = new Scientist(); MadScientist m = new MadScientist();
// p = (Person)p.clone(); // Compile error
// h = (Hero)h.clone(); // Compile error
s = (Scientist)s.clone();
m = (MadScientist)m.clone();
} } ///:~
添加克隆能力之前,编译器会阻止我们的克隆尝试。一旦在