Java编程思想第4版[中文版](PDF格式)-第95部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
成“能够克隆”。而在从我们的类继承的任何场合,clone()方法都是可以使用的,因为Java 不可能在衍生
之后反而缩小方法的访问范围。换言之,一旦对象变得可以克隆,从它衍生的任何东西都是能够克隆的,除
非使用特殊的机制(后面讨论)令其“关闭”克隆能力。
2。 实现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;
353
…………………………………………………………Page 355……………………………………………………………
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);
}
} ///:~
不管怎样,clone()必须能够访问,所以必须将其设为 public (公共的)。其次,作为clone()的初期行动,
应调用 clone()的基础类版本。这里调用的clone()是 Object 内部预先定义好的。之所以能调用它,是由于
它具有 protected (受到保护的)属性,所以能在衍生的类里访问。
Object。clone()会检查原先的对象有多大,再为新对象腾出足够多的内存,将所有二进制位从原来的对象复
354
…………………………………………………………Page 356……………………………………………………………
制到新对象。这叫作“按位复制”,而且按一般的想法,这个工作应该是由 clone()方法来做的。但在
Object。clone()正式开始操作前,首先会检查一个类是否 Cloneable,即是否具有克隆能力——换言之,它
是否实现了 Cloneable 接口。若未实现,Object。clone()就掷出一个 CloneNotSupportedException 违例,指
出我们不能克隆它。因此,我们最好用一个try…catch 块将对 super。clone()的调用代码包围(或封装)起
来,试图捕获一个应当永不出现的违例(因为这里确实已实现了Cloneable 接口)。
在LocalCopy 中,两个方法g()和 f()揭示出两种参数传递方法间的差异。其中,g()演示的是按引用传递,
它会修改外部对象,并返回对那个外部对象的一个引用。而f()是对自变量进行克隆,所以将其分离出来,
并让原来的对象保持独立。随后,它继续做它希望的事情。甚至能返回指向这个新对象的一个句柄,而且不
会对原来的对象产生任何副作用。注意下面这个多少有些古怪的语句:
v = (MyObject)v。clone();
它的作用正是创建一个本地副本。为避免被这样的一个语句搞混淆,记住这种相当奇怪的编码形式在Java 中
是完全允许的,因为有一个名字的所有东西实际都是一个句柄。所以句柄 v 用于克隆一个它所指向的副本,
而且最终返回指向基础类型Object 的一个句柄(因为它在 Object。clone()中是那样被定义的),随后必须
将其造型为正确的类型。
在main()中,两种不同参数传递方式的区别在于它们分别测试了一个不同的方法。输出结果如下:
a == b
a = 12
b = 12
c != d
c = 47
d = 48
大家要记住这样一个事实:Java 对“是否等价”的测试并不对所比较对象的内部进行检查,从而核实它们的
值是否相同。==和!=运算符只是简单地对比句柄的内容。若句柄内的地址相同,就认为句柄指向同样的对
象,所以认为它们是“等价”的。所以运算符真正检测的是“由于别名问题,句柄是否指向同一个对象?”
12。2。5 Object。clone()的效果
调用Object。clone()时,实际发生的是什么事情呢?当我们在自己的类里覆盖 clone()时,什么东西对于
super。clone()来说是最关键的呢?根类中的 clone()方法负责建立正确的存储容量,并通过“按位复制”将
二进制位从原始对象中复制到新对象的存储空间。也就是说,它并不只是预留存储空间以及复制一个对象—
—实际需要调查出欲复制之对象的准确大小,然后复制那个对象。由于所有这些工作都是在由根类定义之
clone()方法的内部代码中进行的(根类并不知道要从自己这里继承出去什么),所以大家或许已经猜到,这
个过程需要用RTTI 判断欲克隆的对象的实际大小。采取这种方式,clone()方法便可建立起正确数量的存储
空间,并对那个类型进行正确的按位复制。
不管我们要做什么,克隆过程的第一个部分通常都应该是调用 super。clone()。通过进行一次准确的复制,
这样做可为后续的克隆进程建立起一个良好的基础。随后,可采取另一些必要的操作,以完成最终的克隆。
为确切了解其他操作是什么,首先要正确理解 Object。clone()为我们带来了什么。特别地,它会自动克隆所
有句柄指向的目标吗?下面这个例子可完成这种形式的检测:
//: Snake。java
// Tests cloning to see if destination of
// handles are also cloned。
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));
355
…………………………………………………………Page 357……………………………………………………………
}
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);
}
} ///:~
一条Snake (蛇)由数段构成,每一段的类型都是Snake。所以,这是一个一段段链接起来的列表。所有段都
是以循环方式创建的,每做好一段,都会使第一个构建器参数的值递减,直至最终为零。而为给每段赋予一
个独一无二的标记,第二个参数(一个Char )的值在每次循环构建器调用时都会递增。
increment()方法的作用是循环递增每个标记,使我们能看到发生的变化;而 toString 则循环打印出每个标
记。输出如下:
s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s。increment; s2 = :a:c:d:e:f
这意味着只有第一段才是由Object。clone()复制的,所以此时进行的是一种“浅层复制”。若希望复制整条
蛇——即进行“深层复制”——必须在被覆盖的clone()里采取附加的操作。
通常可在从一个能克隆的类里调用 super。clone(),以确保所有基础类行动(包括Object。clone())能够进
行。随着是为对象内每个句柄都明确调用一个 clone();否则那些句柄会别名变成原始对象的句柄。构建器
的调用也大致相同——首先构造基础类,然后是下一个衍生的构建器……以此类推,直到位于最深层的衍生
构建器。区别在于 clone()并不是个构建器,所以没有办法实现自动克隆。为了克隆,必须由自己明确进
行。
12。2。6 克隆合成对象
试图深层复制合成对象时会遇到一个问题。必须假定成员对象中的clone()方法也能依次对自己的句柄进行
深层复制,以此类推。这使我们的操作变得复杂。为了能正常实现深层复制,必须对所有类中的代码进行控
制,或者至少全面掌握深层复制中需要涉及的类,确保它们自己的深层复制能正确进行。
356
…………………………………………………………Page 358……………………………………………………………
下面这个例子总结了面对一个合成对象进行深层复制时需要做哪些事情:
//: DeepCopy。java
// Cloning a posed object
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 d