转帖请保留出处:http://blog.csdn.net/u011638883/article/details/11474073
克隆就是创建一份原来对象的备份。字典中的意思是创建一份相同的备份。
默认情况下,Java中的clone是成员变量的复制。但是因为在调用clone()方法并不知道类的成员变量的结构。所以当调用clone时java虚拟机(JVM)会执行以下动作:
1)如果要克隆的类中只包含原始数据类型,那么会得到一个对象的完整克隆,并返回这个克隆对象的引用。
2)如果要克隆的类中成员变量是其他的class类,那么这里只会复制这些成员变量的引用。
java中的克隆实现:
A) 使要克隆对象实现Cloneable接口
B) 重写Object类中的clone()方法【很奇怪,为什么clone方法不是定义在cloneable接口中的】
下面是Java docs中的clone方法:
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object [More ...] clone() throws CloneNotSupportedException;
1) 首先声明保证克隆的对象存储在一个独立的内容地址。
2) 其次声明原始对象和克隆对象应该是同一类型的,但这不是强制要求的。
3) 最后申明原始对象和克隆对象调用equals()方法是应该返回true。但这不是强制要求的。
下面我们来写一个demo试试,
先定义一个要克隆的类Employee
package com.wly.testclone;
public class Employee implements Cloneable {
private Department department;
private String employeeName;
private int employeeId;
public Employee(Department department, String employeeName, int employeeId) {
super();
this.department = department;
this.employeeName = employeeName;
this.employeeId = employeeId;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public int getEmployeeId() {
return employeeId;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
可以看到Employee中包含了一个自定义的Department类,具体如下:
package com.wly.testclone;
public class Department {
private int id;
private String name;
public Department(int id,String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试一下:
package com.wly.testclone;
public class TestCloning {
public static void main(String[] args)
throws CloneNotSupportedException {
Department department = new Department(1, "网络部");
Employee original = new Employee(department, "盖茨-比", 101);
Employee cloned = (Employee) original.clone();
//验证克隆
System.out.println(original != cloned); //输出:true
System.out.println(original.getClass() == cloned.getClass());//输出:true
System.out.println(original.equals(cloned));//输出:false
Employee cloned2 = (Employee)original.clone();
//修改克隆对象中类成员变量的值
cloned2.getDepartment().setName("后勤部");
//输出:后勤部,可见克隆包含"类"的成员变量时的类时,克隆的只是其成员变量的引用对象
System.out.println(original.getDepartment().getName());
}
}
程序运行结果以在注释中标明了,从中可以验证前面提到过了,包含非原始数据类型的成员变量(这里指的是Department)时,clone只是克隆了它的引用对象,那么怎么才能实现“真正”的克隆呢?这里需要了解一下深克隆和浅克隆。
浅复制(Shallow Cloning):是java中的默认实现。重写clone方法,假如你不需要克隆所有的object类型成员变量(不是原始类型),那么你可以使用浅克隆。
深克隆(Deep Cloning):是我们大多数希望实现的克隆方式。我们希望克隆一个独立于原目标对象的对象,从而是在其上的修改不会影响到原对象。
这里我们不妨推理一下怎么实现深克隆,因为在不包含object类型的成员变量的情况下,克隆是能够达到我们的要求的,那么我们只需要找到那个“不听话”的成员变量使其变得“听话”即可。这里我们可以是Department实现Cloneable接口,这样因为Department中不再包含object类型的成员变量,这样Department就实现了完整克隆,从而Employee也就是先了完整的克隆了。具体实现:
1)修改Department,使其实现Cloneable接口,并实现clone接口:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
2)修改Employee中的clone方法:
@Override
protected Object clone() throws CloneNotSupportedException {
Employee cloned = (Employee)super.clone();
cloned.setDepartment((Department)cloned.getDepartment().clone());
return cloned;
}
再运行testCloning即可发现此时对克隆对象的修改已不会影响原对象了。
使用复制构造函数
复制构造函数是一个特殊的构造函数,它使用类的自身类型实例作为构造参数。如新建一个Student类,
package com.wly.testclone;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//复制构造函数
public Student(Student s) {
this.name = s.name;
this.age = s.age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试一下,在testCloning中添加代码
Student s1 = new Student("奥特曼",23);
Student s2 = new Student(s1);
s1.setAge(34);
System.out.println(s2.getAge());
//输出:23
最后,可以使用序列化(Serializable)来实现对象的深克隆,当然使用序列化也有需要注意的地方,首先序列化动作是一个开销很大的操作,其次不是所有的对象都能序列化的,最后序列化是复杂的,可能带来一些意想不到的错误。
@SuppressWarnings("unchecked")
public static T clone(T t) throws Exception {
//检查T是否Serializeble是的实例,否则抛出CloneNotSupportedException
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//序列化
serializeToOutputStream(t, bos);
byte[] bytes = bos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
//反序列化,并返回一个新的实例对象
return (T)ois.readObject();
}
需要注意两点:
1)如果你不确定一个对象是否支持克隆,可以尝试这么写:
if(obj1 instanceof Cloneable) { obj2 = obj1.clone(); };
而不是直接obj2 = (Cloneable)obj1.clone();
2)当你用clone来创建对象时,是不会调用对象的构造方法的,如果你在构造方法里有初始化动作,或者你需要对象被调用的次数,那么在调用clone时你需要仔细一点。
注:翻译原文地址:http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/