今天我们将研究java克隆对象或java中的克隆。 Java Object类附带了原生 clone()
方法,该方法返回现有实例的副本。
Java克隆对象
要使用java对象clone
方法,我们必须实现标记java.lang.Cloneable
接口,使它不会在运行时抛出CloneNotSupportedException
异常。同时对象克隆是受保护的方法,因此我们必须重写它方能在其他类中使用。
我们来看一个例子吧。
package com.journaldev.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
接口。
让我们用一个简单的程序测试我们的java clone对象示例。
package com.journaldev.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("Pankaj");
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()));
// Lets 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:Pankaj
如果我们的Employee类不实现Cloneable
接口,则上面的程序将抛出java.lang.CloneNotSupportedException
的运行时异常。
Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
at java.lang.Object.clone(Native Method)
at com.journaldev.cloning.Employee.clone(Employee.java:41)
at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
java clone对象,java CloneNotSupportedException
让我们看看第一个输出,并了解Object clone()
方法发生了什么,以及它是否存在问题。
emp and clonedEmp == test: false
emp和clonedEmp是两个不同的对象,而不是指同一个对象。这符合java克隆对象的要求。emp and clonedEmp HashMap == test: true
emp和clonedEmp对象变量都引用同一个对象。这带来了克隆的严重问题,接下来我们将看到。clonedEmp props:{city=New York, salary=10000, title=CEO}
请注意,我们没有对clonedEmp属性进行任何更改,但它们仍然被更改,因为emp和clonedEmp变量都引用了同一个对象。这是一个严重的问题,因为java中的默认克隆不会创建完全分离的对象。这可能会导致不需要的结果,因此需要正确重写java克隆对象方法。clonedEmp name:Pankaj
我们更改了emp名称,但是clonedEmp名称没有更改。这是因为String是不可变的。因此,当我们设置emp名称时,会创建一个新字符串并更改emp名称引用this.name = name
。因此clonedEmp名称保持不变。您也会发现所有的基本类型有类似行为。因此,只要我们在对象中只有基本类型和不可变变量,我们就可以使用java clone对象的默认方法。
若对此不太了解的同学可参考深复制与浅复制图解理解
浅克隆
java clone对象的默认实现是使用浅拷贝,类似下面使用反射。
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
深克隆
在深克隆中,我们必须逐个字段进行复制。我们可以如下述代码重写克隆方法进行深克隆。
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;
}
使用此克隆方法,我们的测试程序将产生以下输出。
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
这就是我们想要的,克隆应该返回一个完全脱离原始对象的新对象。因此,如果您正在考虑在程序中使用对象克隆和克隆,请明智地执行此操作并通过处理不可变实例来正确覆盖它。请注意,如果您的类扩展了其他类,而后者又扩展了其他类,那么这可能是一项艰巨的任务。您将不得不一直处理所有不可变字段的深层副本。
使用序列化克隆
轻松执行深克隆的一种方法是通过序列化。但是序列化是一个开销很大的过程,且你的类必须实现Serializable
接口。
使用Apache Commons Util克隆
如果您已在项目中使用Apache Commons Util类,并且您的类是可序列化的,那么请使用以下方法。
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
复制用的构造函数
我们还可以定义复制用的构造函数来获取对象的副本,而不依赖于克隆。例如,我们可以使用如下的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 对象的副本时,我们都可以使用它Employee clonedEmp = new Employee(emp);
。
但是,如果您的类有很多变量,且多数为基本类型和不可变的变量,那么编写复制构造函数可能是一项繁琐的工作。
Java克隆对象的最佳实践
- 仅当您的类具有基本类型和不可变的变量时,才使用默认的Object clone()方法。请注意,这也适用于继承。您必须检查所有要扩展的类,直到根对象Object级别。
- 如果您的类主要由可变对象组成,您还可以定义复制构造函数。
- 通过调用
super.clone()
重写克隆方法来利用Object clone()方法,然后对深复制中的可变字段进行必要的更改。 - 如果您的类是可序列化的,那么您也可以使用序列化进行克隆。然而,它会带来性能损失,因此在使用此方法进行克隆之前,请进行一些基准测试。
- 如果所继承的扩展类已正确定义了使用深克隆的clone方法,则可以使用默认克隆方法。例如,我们在Employee类中正确定义了clone()方法,如下所示。
package com.journaldev.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 {
Object obj = super.clone();
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;
}
}
我们可以有一个像下面这样的子类。
package com.journaldev.cloning;
public class EmployeeWrap extends Employee implements Cloneable {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String t) {
this.title = t;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
请注意,EmployeeWrap
它没有任何可变属性,并且它正在使用父类克隆实现。
这是一个简单的程序来测试这种克隆方式是否正常。
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
EmployeeWrap empWrap = new EmployeeWrap();
empWrap.setId(1);
empWrap.setName("Pankaj");
empWrap.setTitle("CEO");
Map<String, String> props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
empWrap.setProps(props);
EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone();
empWrap.getProps().put("1", "1");
System.out.println("empWrap mutable property value = "+empWrap.getProps());
System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps());
}
}
输出的结果是:
empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
所以它正如我们预期的那样完美。
这都是关于java克隆对象或java中的克隆。我希望您对对象克隆方法有一些了解,以及如何正确重写它而不会产生任何不利影响。
原文地址:Java clone object written by Pankaj
完整代码:Github
参考资料:API Doc for Object clone