Java漫谈-深拷贝与浅拷贝
创建对象的方式
1、调用new语句创建对象,最常见的一种 2、运用反射手段创建对象,调用java.lang.Class 或者 java.lang.reflect.Constructor 类的newInstance()实例方 3、调用对象的clone()方法 4、使用反序列化,调用java.io.ObjectInputStream 对象的 readObject()方法.
将一个对象的引用复制给另外一个对象的方法
1、直接赋值 2、浅拷贝 3、深拷贝
直接赋值
实体类Person.java
public class Person {
//姓名
private String name;
// 年龄
private int age;
// 编号
private Integer code;
public Person(){
}
public Person(String name, int age, Integer code) {
this.name = name;
this.age = age;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
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
测试类cloneDemo.java 本篇所有测试均会在该类下完成
public class cloneDemo {
public static void main(String[] args) {
Person p = new Person("小明",11,123);
Person p1 = p;
System.out.println(p);
System.out.println(p1);
}
}
2
3
4
5
6
7
8
打印结果:
Others.base.cloneDemo.Person@4554617c
Others.base.cloneDemo.Person@4554617c
2
两者打印出来的地址相同,可见二者的引用是同一个对象,并没有创建出一个新的对象。可以把这种现象叫做引用的复制(或引用拷贝)。 对Person.java追加toString方法,
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
'}';
}
2
3
4
5
6
7
8
cloneDemo.java中追加
p1.setCode(1234);
System.out.println("--------windcoder.com----------");
System.out.println(p);
System.out.println(p1);
2
3
4
从打印结果中更能体现出这一点,因为是同一个对象的引用,所以两者改一个,另一个对象的值也随之改变:
Person{name='小明', age=11, code=123}
Person{name='小明', age=11, code=123}
--------windcoder.com----------
Person{name='小明', age=11, code=1234}
Person{name='小明', age=11, code=1234}
2
3
4
5
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。 如果属性是基本类型,拷贝的就是基本类型的值; 如果属性是内存地址(引用类型),拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。 实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法。 下面改造Person.java
/**
* 实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法
*/
public class Person implements Cloneable{
//姓名
private String name;
// 年龄
private int age;
// 编号
private Integer code;
public Person(){
}
public Person(String name, int age, Integer code) {
this.name = name;
this.age = age;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
'}';
}
/**
* 覆写clone()方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
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
cloneDemo.java中追加
Person p2 = p;
p2.setName("小明克隆人");
p2.setAge(12);
p2.setCode(12345);
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
2
3
4
5
6
7
得到结果:
Person{name='小明', age=11, code=1234}
Person{name='小明克隆人', age=12, code=12345}
--------windcoder.com----------
2
3
此时p2完成了一次拷贝。现在继续改造Person,添加一个Address对象。 Address.java
public class Address {
private String name;
public Address( ) {
}
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
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
Person.java中添加对应属性
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 修改toString,追加address
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
", address=" + address +
'}';
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cloneDemo.java追加如下:
p.setAddress(new Address("我是一个小学生"));
Person p2 = (Person)p.clone();
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
p2.setName("小明克隆人");
p2.setAge(12);
p2.setCode(12345);
p2.getAddress().setName("我是一个小学生克隆人");
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
2
3
4
5
6
7
8
9
10
11
12
我们预期是能够获得p还是原来的address,p2的address发生改变,但结果如何呢,打印发现两者都发生了改变:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生克隆人'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------
2
3
4
5
6
7
这是由于,此时发生的仅是浅拷贝,即被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。针对基本类型及其封装类都是将对应的基本类型值拷贝,对于其余对象,则仅是拷贝其引用,这导致最终里面的对象是同一个,更改一个,另一个的拷贝/原对象的对应值也随之更改。
深拷贝
如果想将上述的address也单独复制一份,这就用到了深拷贝。 被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。 通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。 深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。
改造方案
Address.java 实现Cloneable接口并重写clone
public class Address implements Cloneable{
private String name;
public Address( ) {
}
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
/**
* 覆写clone()方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
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
Person.java修改clone方法:
protected Object clone() {
try {
// super.clone();
// return super.clone();
// 改为深复制:
Person newPerson = (Person)super.clone();
// 拷贝一份新的address
newPerson.address = (Address) address.clone();
return newPerson;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
此时再运行之前的cloneDemo.java,发现结果已经是预期的效果:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------
2
3
4
5
6
7
拓展:利用串行化来做深复制
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
public Object deepClone() {
//写入对象
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
2
3
4
5
6
7
8
9
10
改造示例
Address追加Serializable接口实现
public class Address implements Serializable, Cloneable{
.....
}
2
3
Person追加Serializable接口实现
public class Person implements Serializable, Cloneable{
......
/**
* 利用串行化来做深复制
* by: windCoder.com
* @return
*/
public Object deepClone() {
try {
//写入对象
ByteArrayOutputStream bo= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bo);
os.writeObject(this);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
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
cloneDemo.java追加:
Person p3 = (Person)p.deepClone();
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");
p3.setName("小明克隆人");
p3.setAge(12);
p3.setCode(12345);
p3.getAddress().setName("我是一个小学生克隆人233333");
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");
2
3
4
5
6
7
8
9
10
11
运行结果:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人233333'}}
--------windcoder.com----------
2
3
4
5
6
7
注:可以使用transient关键字,修饰不需要序列化的属性。当序列化对象的时候,这个属性就不会序列化到指定的目的地中。
参考资料
除特别注明外,本站所有文章均为 windcoder 原创,转载请注明出处来自: javashenkaobeiyuqiankaobei

暂无数据