Java 对象 clone() 方法 - Java 中的克隆
克隆是创建对象副本的过程。Java Object 类带有本机clone()
方法,可返回现有实例的副本。由于 Object 是 Java 中的基类,因此所有对象默认都支持克隆。
Java 对象克隆
如果要使用 Java Object clone() 方法,则必须实现java.lang.Cloneable
标记接口。否则,它将CloneNotSupportedException
在运行时抛出。此外,Object clone 是一种受保护的方法,因此您必须覆盖它。让我们通过一个示例程序来了解 Java 中的对象克隆。
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
。我们正在调用超类 clone() 方法,即 Object clone() 方法。
使用 Object clone() 方法
让我们创建一个测试程序,使用对象 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()));
// 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:Pankaj
运行时出现 CloneNotSupportedException
如果我们的 Employee 类不实现Cloneable
接口,上述程序将抛出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)
理解对象克隆
让我们看一下上面的输出并了解 Objectclone()
方法发生了什么。
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 变量都引用同一个对象。发生这种情况是因为默认的 Object clone() 方法创建了一个浅表副本。当您想通过克隆过程创建完全分离的对象时,这可能会成为一个问题。这可能会导致不必要的结果,因此需要正确覆盖 Object clone() 方法。clonedEmp name:Pankaj
:这里发生了什么?我们更改了 emp 名称,但 clonedEmp 名称没有改变。这是因为String 是不可变的。因此,当我们设置 emp 名称时,会创建一个新的字符串,并且 emp 名称引用会更改this.name = name;
。因此 clonedEmp 名称保持不变。您也会发现任何原始变量类型都有类似的行为。因此,只要对象中只有原始和不可变变量,我们就可以很好地使用 java 对象默认克隆。
对象克隆类型
对象克隆有两种类型:浅克隆和深克隆。让我们了解每种类型,并找出在 Java 程序中实现克隆的最佳方法。
1.浅层克隆
The default implementation of Java Object clone() method is using shallow copy. It’s using reflection API to create the copy of the instance. The below code snippet showcase the shallow cloning implementation.
@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. Deep Cloning
In deep cloning, we have to copy fields one by one. If we have a field with nested objects such as List, Map, etc. then we have to write the code to copy them too one by one. That’s why it’s called deep cloning or deep copy. We can override the Employee clone method like the following code for deep cloning.
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;
}
With this clone() method implementation, our test program will produce the following output.
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
In most of the cases, this is what we want. The clone() method should return a new object totally detached from the original instance. So if you are thinking to use Object clone and cloning in your program, do it wisely and override it properly by taking care of mutable fields. It could be a daunting task if your class extends other class that in turn extends other class and so on. You will have to go all the way in the Object inheritance hierarchy to take care of the deep copy of all the mutable fields.
Cloning using Serialization?
One way to easily perform deep cloning is through serialization. But serialization is an expensive procedure and your class should implement Serializable
interface. All the fields and superclasses must implement Serializable too.
Using Apache Commons Util
If you are already using Apache Commons Util classes in your project and your class is serializable, then use the below method.
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
Copy Constructor for Cloning
We can define a copy constructor to create a copy of the object. Why to depend on the Object clone() method at all? For example, we can have an Employee copy constructor like the following code.
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);
}
Whenever we need a copy of employee object, we can get it using Employee clonedEmp = new Employee(emp);
. However writing copy constructor can be a tedious job if your class has a lot of variables, especially primitive and immutable.
Java Object Cloning Best Practices
-
Use default Object clone() method only when your class has primitives and immutable variables or you want shallow copy. In case of inheritance, you will have to check all the classes you are extending till the Object level.
-
You can also define copy constructor if your class has mostly mutable properties.
-
Utilize Object clone() method by calling
super.clone()
in overridden clone method, then make necessary changes for deep copying of mutable fields. -
If your class is serializable, you can use serialization for cloning. However, it will come with a performance hit, so do some benchmarking before using serialization for cloning.
-
If you are extending a class and it has defined clone method properly using deep copy, then you can utilize default clone method. For example, we have properly defined clone() method in Employee class as follows.
@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
没有任何可变属性,并且它利用超类 clone() 方法实现。如果有可变字段,那么您只需要关注这些字段的深度复制。这里有一个简单的程序来测试这种克隆方式是否正常工作。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 Object clone() 方法以及如何正确覆盖它而不产生任何不利影响有所了解。
您可以从我的GitHub 存储库下载该项目。
参考:对象克隆的 API 文档