Java Object clone()方法–用Java克隆
克隆是创建对象副本的过程。
Java Object类带有本机的clone()方法,该方法返回现有实例的副本。
由于Object是Java中的基类,因此默认情况下所有对象都支持克隆。
Java对象克隆
如果要使用Java Object clone()方法,则必须实现java.lang.Cloneable标记接口。
否则,它将在运行时抛出CloneNotSupportedException。
同样,对象克隆也是一种受保护的方法,因此您将不得不覆盖它。
让我们来看一个示例程序中的Java对象克隆。
package com.theitroad.cloning;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Employee implements Cloneable {
private int id;
private String name;
private Map<String, String> props;
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;
}
public Map<String, String> getProps() {
return props;
}
public void setProps(Map<String, String> p) {
this.props = p;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
我们正在使用Object clone()方法,所以我们实现了Cloneable接口。
我们正在调用超类clone()方法,即对象clone()方法。
使用对象clone()方法
让我们创建一个测试程序,以使用对象clone()方法创建实例的副本。
package com.theitroad.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp = new Employee();
emp.setId(1);
emp.setName("hyman");
Map<String, String> props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
emp.setProps(props);
Employee clonedEmp = (Employee) emp.clone();
//Check whether the emp and clonedEmp attributes are same or different
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
//Let's see the effect of using default cloning
//change emp props
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
//change emp name
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
输出:
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:hyman
运行时发生CloneNotSupportedException
如果我们的Employee类不会实现Cloneable接口,则上述程序将抛出CloneNotSupportedException运行时异常。
Exception in thread "main" java.lang.CloneNotSupportedException: com.theitroad.cloning.Employee at java.lang.Object.clone(Native Method) at com.theitroad.cloning.Employee.clone(Employee.java:41) at com.theitroad.cloning.CloningTest.main(CloningTest.java:19)
了解对象克隆
让我们看一下上面的输出,并了解Objectclone()方法的情况。
emp和clonedEmp == test:false 。
:这意味着emp和clonedEmp是两个不同的对象,而不是引用同一对象。
这与Java对象克隆要求一致。emp和clonedEmp HashMap == test:true:因此emp和clonedEmp对象变量都引用同一对象。
如果我们更改基础对象的值,这可能是一个严重的数据完整性问题。
值的任何更改也可能会反映到克隆的实例中。clonedEmp props:{city = New York,salary = 10000,title = CEO}}:我们没有对clonedEmp属性进行任何更改,但是由于emp和clonedEmp变量都指向同一个对象,因此它们得到了更改。
发生这种情况是因为默认的Object clone()方法创建了浅表副本。
当您要通过克隆过程创建完全分离的对象时,可能会出现问题。
这可能会导致不想要的结果,因此需要正确地重写Object clone()方法。clonedEmp name:hyman:这是怎么回事?我们更改了emp名称,但clonedEmp名称未更改。
这是因为String是不可变的。
因此,当我们设置emp名称时,将创建一个新字符串并在'this.name = name;'中更改emp名称引用。
因此clonedEmp名称保持不变。
对于任何原始变量类型,您也会发现类似的行为。
因此,只要对象中只有原始变量和不可变变量,我们就擅长使用Java对象默认克隆。
对象克隆类型
有两种类型的对象克隆–浅克隆和深克隆。
让我们了解它们的每一个,并找出在我们的Java程序中实现克隆的最佳方法。
1.浅克隆
Java Object clone()方法的默认实现是使用浅表复制。
它使用反射API创建实例的副本。
下面的代码片段展示了浅层克隆实现。
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
2.深克隆
在深度克隆中,我们必须一一复制字段。
如果我们有一个带有嵌套对象(例如List,Map等)的字段,那么我们必须编写代码以将它们也一张一张地复制。
这就是为什么将其称为深度克隆或者深度复制。
我们可以像下面的代码那样覆盖Employee克隆方法,以进行深度克隆。
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
//deep cloning for immutable fields
emp.setProps(null);
Map<String, String> hm = new HashMap<>();
String key;
Iterator<String> it = this.props.keySet().iterator();
//Deep Copy of field by field
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
使用此clone()方法实现,我们的测试程序将产生以下输出。
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:hyman
在大多数情况下,这就是我们想要的。
clone()方法应返回一个与原始实例完全分离的新对象。
因此,如果您打算在程序中使用对象克隆和克隆,请明智地进行操作,并通过照顾可变字段来适当地覆盖它。
如果您的程序扩展了其他程序,又扩展了其他程序,这可能是一项艰巨的任务。
您将必须在对象继承层次结构中进行所有操作,以照顾所有可变字段的深层副本。
使用序列化进行克隆?
轻松执行深度克隆的一种方法是序列化。
但是序列化是一个昂贵的过程,您的类应实现" Serializable"接口。
所有字段和超类也必须实现Serializable。
使用Apache Commons Util
如果您已在项目中使用Apache Commons Util类,并且该类可序列化,则使用以下方法。
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
复制构造函数以进行克隆
我们可以定义一个复制构造函数来创建对象的副本。
为什么要完全依赖Object clone()方法?
例如,我们可以有一个类似于以下代码的Employee复制构造函数。
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map<String, String> hm = new HashMap<>();
String key;
Iterator<String> it = emp.getProps().keySet().iterator();
//Deep Copy of field by field
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
每当我们需要员工对象的副本时,都可以使用" Employee clonedEmp = new Employee(emp);"来获取它。
但是,如果您的类具有很多变量,尤其是原始变量和不可变变量,那么编写拷贝构造函数可能是一项繁琐的工作。
Java对象克隆最佳实践
仅当您的类具有基元和不可变变量或者需要浅表复制时,才使用默认的Object clone()方法。
在继承的情况下,您将必须检查所有扩展到对象级别的类。如果您的类主要具有可变属性,则也可以定义复制构造函数。
通过在覆盖的克隆方法中调用
super.clone()来利用Object clone()方法,然后进行必要的更改以对可变字段进行深度复制。如果您的类可序列化,则可以使用序列化进行克隆。
但是,它将带来性能上的损失,因此在使用序列化进行克隆之前,请进行一些基准测试。如果要扩展类,并且使用深层复制正确定义了克隆方法,则可以使用默认克隆方法。
例如,我们在Employee类中正确定义了clone()方法,如下所示。
我们可以创建一个子类,并利用超类进行深度克隆,如下所示。
" EmployeeWrap"类没有任何可变的属性,它利用超类clone()方法实现。
如果存在可变字段,则只需要对这些字段进行深度复制即可。
这里有一个简单的程序来测试这种克隆方式是否正常工作。

