本文由 ImportNew - 刘志军 翻译自 javarevisited。如需转载本文,请先参见文章末尾处的转载要求。
clone()是java.lang.Object类下面的一个很难处理的方法,clone()的作用很简单,提供一种克隆机制
创建对象的副本,对于如何实现它已成为一个棘手的事,同时还长期被广受批评。不管怎样,我们不去争论历史,现在我们将尝试学习clone方法是怎样工作的。说实在的,想
理解克隆机制并不简单,甚至有经验的java
程序员也很难解释可变对象的克隆是如何
运作的、深克隆(deep copy)与浅克隆(shallow copy)的区别。这文章分为三部分,我们首先看clone方法是如何工作的,第二部分将学习如何重写(override)clone方法,最后我们讨论深度克隆与浅克隆。之所以选择把它作为三部分,是为了一次专注在一件事上,clone()本身就很让人困惑的,因此最好是一个一个理解概念。在这篇文章中将学习到什么是clone方法,以及怎么工作的,顺便说一句,clone是定义一个Object类下基本方法之一,与之类似的还有
hashcode(),toString(),wait和notify。
什么是克隆对象
clone()方法返回的对象叫做原始对象的克隆体。一个克隆对象的基本特性必须是:a.clone()!=a,这也就意味着克隆对象和原始对象在java 堆(heap)中是两个独立的对象。a.clone().getClass == a.getClass() 以及 clone.equals(a),也就是说克隆对象完完全全是原始对象的一个拷贝。这些特征来自于一种良好的行为—正确地重写(overriedden)clone方法。但是这些并不是克隆机制强制要求的。意味着clone()返回的对象可能会违反这些约定(通过调用super.clone()方法返回的对象),当重写clone()方法时,你可以遵循前面两条(a.clone()!=a和a.clone().getClass()==a.getClass())。为了遵循第三个特性(clone.equals(a)),你必须重写equals方法。例如:Rectangle类的克隆对象就有这三个特性,但是如果你
注释equals()再来运行相同的程序,你将看到第三个约束条件clone.equlas(a)返回false,顺便说一下,在《Effective Java》中有条目提到如何有效使用clone方法的知识点,我强烈建议你在读完本文后去翻一翻这本书。
clone方法是如何工作的
java.lang.Object提供了默认的clone方法实现,它声明为protected和native。因此它的实现是取决于本地代码,因为它约定返回对象是通过调用super.clone()方法,任何克隆的过程最终都将到达java.lang.Object 的clone()方法,它首先检查这个相关的类是否实现了Cloneable
接口,这个接口是一个标记接口,如果这个实例没有实现cloneable接口,那么就会抛出CloneNotSupported
异常,这个异常是一个checked异常,也就是说他在克隆对象的时候总需要被处理。如果没有异常抛出,然后java.lang.Object的clone()方法将创建一个拷贝返回给调用者。因为Object类的clone()方法通过创建新对象来生成这个副本的,然后逐个域拷贝(field-by-filed),类似于赋值操作,这种操作对于原始类型(primitives)和不可变类型(immutable)来说是没问题的,但是如果你的类包含一些可变的数据结构如:ArrayList或数组就不合适了,这种情况原始对象和副本对象将指向相同的堆,你可以通过一种叫深度克隆的技术防止这种事情发生。他的每一个可变的域被独立的克隆,简而言之,下面总结就是一个clone方法是如何工作的。
1. 任何类在实例上调用clone(),他将实现cloneable重写clone方法,创建副本。
1
Rectangle rec = new Rectangle(30, 60);
2
logger.info(rec);
3
4
try {
5
logger.info("Creating Copy of this object using Clone method");
6
Rectangle copy = rec.clone();
7
logger.info("Copy " + copy);
8
9
} catch (CloneNotSupportedException ex) {
10
logger.
debug("Cloning is not supported for this object");
11
}
2. 在Rectangle中调用clone方法被委派给super.clone(),它可以是
自定义的super
class或者是默认的java.lang.Object.
1
@Override
2
protected Rectangle clone() throws CloneNotSupportedException {
3
return (Rectangle) super.clone();
4
}
1
最后调用到达java.lang.Object的clone()时,他验证相关的类是否实现Cloneable接口,如果没有实现那么抛出CloneNotSupportedException,否则他创建副本filed-by-field。
所以为了clone()方法可以正确的工作,两件事会发生:类必须实现Cloneable接口,必须重写clone方法,这是最简单的
例子,对于更复杂的对象,它包含多个fileds、数组,
结合不可变对象和原始类型,我们来看第二个clone教程
clone()举例
在这篇文章中,我们没有看到复杂的重写clone的方法,因为我们的Rectangle类非常简单,仅仅只是包含原生类型,只需通过Object的clone方法浅克隆就足够了。但是下面这个例子对理解对象的克隆很重要,这里是完整的代码:
1
import org.apache.log4j.Logger;
2
3
/**
4
* Simple example of overriding clone() method in Java to understand How Cloning of
5
* Object works in Java.
6
*
7
* @author
8
*/
9
public class JavaCloneTest {
10
private static final Logger logger = Logger.getLogger(JavaCloneTest.class);
11
12
public static void main(String args[]) {
13
14
Rectangle rec = new Rectangle(30, 60);
15
logger.info(rec);
16
17
Rectangle copy = null;
18
try {
19
logger.info("Creating Copy of this object using Clone method");
20
copy = rec.clone();
21
logger.info("Copy " + copy);
22
23
} catch (CloneNotSupportedException ex) {
24
logger.debug("Cloning is not supported for this object");
25
}
26
27
//testing properties of object returned by clone method in Java
28
logger.info("copy != rec : " + (copy != rec));
29
logger.info("copy.getClass() == rec.getClass() : " + (copy.getClass() == rec.getClass()));
30
logger.info("copy.equals(rec) : " + copy.equals(rec));
31
32
//Updating fields in
original object
33
rec.setHeight(100);
34
rec.setWidth(45);
35
36
logger.info("Original object :" + rec);
37
logger.info("Clonned object :" + copy);
38
}
39
40
}
41
42
public class Rectangle implements Cloneable{
43
private int width;
44
private int height;
45
46
public Rectangle(int w, int h){
47
width = w;
48
height = h;
49
}
50
51
public void setHeight(int height) {
52
this.height = height;
53
}
54
55
public void setWidth(int width) {
56
this.width = width;
57
}
58
59
public int area(){
60
return widthheight;
61
}
62
63
@Override
64
public String toString(){
65
return String.format("Rectangle [width: %d, height: %d, area: %d]", width, height, area());
66
}
67
68
@Override
69
protected Rectangle clone() throws CloneNotSupportedException {
70
return (Rectangle) super.clone();
71
}
72
73
@Override
74
public boolean equals(Object obj) {
75
if (obj == null) {
76
return false;
77
}
78
if (getClass() != obj.getClass()) {
79
return false;
80
}
81
final Rectangle other = (Rectangle) obj;
82
if (this.width != other.width) {
83
return false;
84
}
85
if (this.height != other.height) {
86
return false;
87
}
88
return true;
89
}
90
91
@Override
92
public int hashCode() {
93
int hash = 7;
94
hash = 47 hash + this.width;
95
hash = 47 hash + this.height;
96
return hash;
97
}
98
99
}
100
101
Output:
102
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - Rectangle [width: 30, height: 60, area: 1800]
103
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - Creating Copy of this object using Clone method
104
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - Copy Rectangle [width: 30, height: 60, area: 1800]
105
106
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - copy != rec : true
107
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - copy.getClass() == rec.getClass() : true
108
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - copy.equals(rec) : true
109
110
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - Original object :Rectangle [width: 45, height: 100, area: 4500]
111
112
2013-05-20 23:46:58,882 0 [main] INFO JavaCloneTest - Cloned object :Rectangle [width: 30, height: 60, area: 1800]
从输出结果,你可以清晰的看到克隆对象和原始对象有相同的属性,同样改变原始对象的属性并不影响拷贝对象的状态。因为
他们仅仅包含原生字段,包含任何可变对象将影响两者,你同样还可以看到标准的克隆对象的属性如:clone!=original,clone.getClass()==original.getClass(),clone.equals(original).
要记住的事情
克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.
在克隆java对象的时候不会调用
构造器
java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含可变对象,那么原始对象和克隆都将指向相同的内部对象,这是很
危险的,因为发生在可变的字段上任何改变将反应到原始对象和副本对象上。为了避免这种情况,重写clone()方法。
按照约定,实例的克隆应该通过调用super.clone()获取,这样有助克隆对象的不变性建如:clone!=original和clone.getClass()==original.getClass(),尽管这些不是必须的