深拷贝和浅拷贝

[TOC]

cloneable

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类
实例的 clone() 方法。

1
2
3
4
5
6
public class CloneExample {
private int a;
private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'

正确:

1
2
3
4
5
6
7
8
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

浅拷贝

对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

/clone-qian.png

我们举个clone产生的浅拷贝的例子,我们定义一个对象中的对象,然后尝试拷贝:

1
2
3
4
5
6
7
8
9
10
11
@Data
public class Address implements Cloneable{
private String name;

//不是好的方式
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();

}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class CustUser implements Cloneable{
private String firstName;
private String lastName;
private Address address;
private String[] cars;

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

上面的例子中,我们定义了CustUser和Address。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void testShallowCopy() throws CloneNotSupportedException {
Address address= new Address();
address.setName("北京天安门");
CustUser custUser = new CustUser();
custUser.setAddress(address);
custUser.setLastName("李");
custUser.setFirstName("雷");
String[] cars = new String[]{"别克","路虎"};
custUser.setCars(cars);

CustUser custUserCopy=(CustUser) custUser.clone();
custUserCopy.setFirstName("梅梅");
custUserCopy.setLastName("韩");
custUserCopy.getAddress().setName("北京颐和园");
custUserCopy.getCars()[0]="奥迪";

log.info("{}",custUser);
log.info("{}",custUserCopy);
}

浅拷贝我们只调用了CustUser的clone方法。看下输出结果:

1
2
3
4
CustUser(firstName=雷, lastName=李, address=Address(name=北京颐和园), cars=[奥迪, 路虎])

CustUser(firstName=梅梅, lastName=韩, address=Address(name=北京颐和园), cars=[奥迪, 路虎])
复制代码

我们可以看到拷贝之后的Address变化会影响到被拷贝的对象。

上面的例子我们还要关注两个点:第一点String是不可变的。不管是拷贝还是赋值,String都是不可变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  class Singleton implements  Cloneable
{
int x;
int[] arr =new int[1];

@Override
protected Object clone() throws CloneNotSupportedException {
Singleton s= (Singleton)super.clone();
s.arr=this.arr.clone();
return s;

}

public static void main(String[] args) throws CloneNotSupportedException {
Singleton s1=new Singleton();
Singleton s2=(Singleton) s1.clone();
s1.x=1;
s2.x=2;
s1.arr[0]=1;
s2.arr[0]=3;
System.out.println(s1.x+" "+s2.x);
System.out.println(s1.arr[0]+" "+s2.arr[0]);
}
}

深拷贝

对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

/clone-深.png

用序列化进行深拷贝

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
56
57
58
59
60
61
62
63
64
65
66
public class Person implements Serializable {

private static final long serialVersionUID = 369285298572941L;
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;

private PersonDesc personDesc;

public Person clone() {
Person person = 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);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}

public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
...省略...
}
public class PersonDesc implements Serializable {

private static final long serialVersionUID = 872390113109L;
// 描述
private String desc;

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

}
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头哥",20,"123456@qq.com","我的公众号是:平头哥的技术博文");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头哥的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了平头哥的技术博文公众号");
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}