Java的对象克隆

zjun Lv4

Java中的不变对象 提到了在生成不变对象时,如果类中有引用类型的应用,那么在初始化传入参数和返回引用属性时,会通过引用类型的clone()方法克隆一个新的对象,从而隔离属性的引用“逃逸”到不变对象之外,起到改变不变对象内部状态的结果。但是clone()方法默认实现的是浅拷贝,如果存在多个嵌套对象的话,那需要考虑实现深拷贝

浅拷贝

浅拷贝,只拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用对象。拷贝出来的新对象中的引用类型变量还是指向原来的对象。比如下面这个例子:
浅拷贝的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Employee {  
private String SN; // 员工号
private String name; // 员工姓名
private Date birthday; // 员工生日

public Employee(String SN, String name, Date birthday){
this.SN = SN;
this.name = name;
this.birthday = birthday;
}

public String getSN(){
return SN;
}

public String getName(){
return name;
}

public Date getBirthday(){
return birthday;
}

public Object clone() throws CloneNotSupportedException {
return (Employee)super.clone();
}

public static void main(String[] args) throws InterruptedException {
Employee emp = new Employee("123", "John", new Date());
Employee cloneEmp = (Employee)emp.clone();
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
}
}

运行结果:

1
2
3
Wed Aug 15 21:03:37 CST 2018
Wed Aug 15 21:03:38 CST 2018
Wed Aug 15 21:03:39 CST 2018

可以看到Employee.birthday这个引用类型的变量并没有被克隆一个新的对象,而还是指向了原来的对象,所以“逃逸”到了Employee对象外面,从而导致了可以在对象外部修改birthday这个属性。为了防止这种情况,所以我们在拷贝带有引用类型属性的对象时,通常会实现深拷贝

深拷贝

深拷贝,会拷贝对象的所有的值(包括引用类型的值),即使源对象发生任何改变,拷贝的值也不会变化。
深拷贝的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Employee {  
private String SN; // 员工号
private String name; // 员工姓名
private Date birthday; // 员工生日

public Employee(String SN, String name, Date birthday){
this.SN = SN;
this.name = name;
this.birthday = birthday;
}

public String getSN(){
return SN;
}

public String getName(){
return name;
}

public Date getBirthday(){
return birthday;
}

public Object clone() throws CloneNotSupportedException {
return (Employee)super.clone();
}

public static void main(String[] args) throws InterruptedException {
Employee emp = new Employee("123", "John", new Date());
Employee cloneEmp = (Employee)emp.clone();
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
}
}

运行结果:

1
2
3
Wed Aug 15 21:33:13 CST 2018
Wed Aug 15 21:33:13 CST 2018
Wed Aug 15 21:33:13 CST 2018

这里使用的是重载clone()方法,手动对birthday属性进行clone,并赋值给新的Employee对象。这是最简单也最清楚易懂的实现方式,但是有一个问题就是如果存在很多层的嵌套对象,列如:department.employee.address 这种嵌套对象的话,需要重载每一个引用的对象,增加代码量,而且容易遗忘和出错。
在面对多层克隆的时候,还可以通过序列化来实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Employee implements Serializable {  
private static final long serialVersionUID = 369385298572951L;
private String SN; // 员工号
private String name; // 员工姓名
private Date birthday; // 员工生日

public Employee(String SN, String name, Date birthday){
this.SN = SN;
this.name = name;
this.birthday = birthday;
}

public String getSN(){
return SN;
}

public String getName(){
return name;
}

public Date getBirthday(){
return birthday;
}

public Employee clone() {
Employee emp = null;
try{
// 将该对象序列化成流, 写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
emp = (Employee) ois.readObject();
}catch (IOException e) {
throw new RuntimeException(e);
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
return emp;
}

public static void main(String[] args) throws InterruptedException, CloneNotSupportedException {
Employee emp = new Employee("123", "John", new Date());
Employee cloneEmp = (Employee)emp.clone();
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
Thread.sleep(1000);
emp.getBirthday().setTime(System.currentTimeMillis());
System.out.println(cloneEmp.getBirthday());
}
}

运行结果:

1
2
3
Wed Aug 15 21:46:09 CST 2018
Wed Aug 15 21:46:09 CST 2018
Wed Aug 15 21:46:09 CST 2018

可以通过 Apache Commons Lang中的 SerializationUtils来简化序列化深拷贝的过程:

1
Employee emp1 = (Employee)SerializationUtils.clone(emp);
  • 标题: Java的对象克隆
  • 作者: zjun
  • 创建于 : 2018-08-15 20:37:27
  • 更新于 : 2023-12-05 21:59:21
  • 链接: https://zjun.site/2018/08/c4e4e37b1de9.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
Java的对象克隆