以前经常会看见java“深拷贝”和“浅拷贝”的字眼,不如现在从创建到拷贝来好好捋一捋,学习一下关于java深浅拷贝的知识。

浅拷贝:以某个对象为基准创建新对象,将其所有非静态属性复制到新对象。如果属性是值类型(基本数据类型),则直接复制该字段;如果属性是引用类型,则复制引用但不复制引用的对象。

深拷贝:以某个对象为基准创建新对象,将其所有非静态属性复制到新对象。如果属性是值类型(基本数据类型),则直接复制该字段;如果属性是引用类型,则复制引用的对象

插话:对于很久没有更新博客,谢一波罪。主要是最近在学的东西都更新在了笔记系统上,很久没有写博客作技术分享了。但博客是博客,笔记是笔记,一个是输出,一个是输入,无论如何不能把搭建博客的初心给忘了。🌝🌝待笔记逐渐完善后,我会分享链接至导航栏,敬请关注! 那我们现在开始吧~~👇👇


前言:在面向对象编程中,我们使用最频繁的是创建对象,而比较少用同样是得到一个新对象的对象拷贝的方式,即便需要拷贝,我们也可能是使用有参构造方法来新建一个对象。虽然如此,但是理解对象的拷贝对于理解java世界中对象的创建、参数的传递和复制以及对象在内存中的存在方式等方面有很大的帮助。

从我们最熟悉的创建去认识既熟悉又陌生的拷贝。

回顾:java创建对象的方式

在java中创建对象有五种方式:

  1. 使用new关键字;
  2. 通过Class类newInstance方法
  3. 通过Constructor类newInstance方法
  4. 利用Clone方法
  5. 通过反序列化实现。

一、理解对象的拷贝、浅拷贝、深拷贝

拷贝In object-oriented programming, object copying is creating a copy of an existing object, a unit of data in object-oriented programming. 在面向对象编程中,对象是面向对象编程中的数据单元,对象复制是创建现有对象的副本。——Wikipedia对拷贝的定义

个人觉得这个定义很纯粹、很真实。它抛弃了我们关于对象拷贝关于是否是引用还是值传递的相关忧虑,回归拷贝的本质——即复制。

但在java中,它并不那么纯粹。我们进行拷贝操作的目的是为了得到一个现有对象的副本,但由于java涉及内存引用的复杂的实际问题,它给我们提供了两种不同的拷贝方法来得到两种不同的副本————深拷贝和浅拷贝。

(:这里仅讨论深浅拷贝,暂时忽略延迟拷贝等

浅拷贝:以某个对象为基准创建新对象,将其所有非静态属性复制到新对象。如果属性是值类型(基本数据类型),则直接复制该字段;如果属性是引用类型,则复制引用地址

释义:
浅拷贝People对象得到一个副本CopyPeople,由于id是int型,属于基本类型,故CopyPeople直接复制一份。而info对象是Info型,复制其引用地址,最后共用一个Info对象。

深拷贝:以某个对象为基准创建新对象,将其所有非静态属性复制到新对象。如果属性是值类型(基本数据类型),则直接复制该字段;如果属性是引用类型,则复制引用的对象

释义:
深拷贝People对象得到一个副本CopyPeople,由于id是int型,属于基本类型,故CopyPeople直接复制一份。而info对象是Info型,根据其引用新创建一个对象。

拟人独白

浅拷贝:

CopyPerson:Person,我们一起用info吧?

Person:好的!

深拷贝:

CopyPerson:Person,我们一起用info吧?

Person:不行,我想自己用!

CopyPerson:好吧,那我自己new一个好了。

究其差别:无非是拷贝的是引用对象时的不同——浅拷贝复制对象的引用,大家一起使用这个对象;而深拷贝直接复制一个新的对象不抢占原拷贝的对象,拥有自己专属的一个对象。

二、浅拷贝方法及实例

利用Object类的clone方法可以实现拷贝。该方法是创建并返回调用类的一个拷贝。

这里利用原生的clone方法实现上述Person对象的浅拷贝。

Info

Info类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Info {
String name;
int age;
public String getName(){
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public Info(String name, int age){
this.age=age;
this.name=name;
}
}

Person

Person类

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
public class Person implements Cloneable {
int id;
// 引用对象
Info info;

public Person(int id, String name, int age) {
this.id = id;
this.info = new Info(name, age);
}

@Override
protected Object clone(){
// 浅拷贝
try {
// 调用父类的clone方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
// get、set方法
public int getId() {
return id;
}

public Person() {
}

public void setId(int id) {
this.id = id;
}

public Info getInfo() {
return info;
}
}

shadowCopy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class shadowCopy {
public static void main(String[] args) {
// 初始化一个对象
Person person = new Person(1, "Tom", 20);
System.out.println("person原始数据:"+person.getId() + "-" + person.getInfo().getName() + "-" + person.getInfo().getAge());

// 拷贝一个对象
Person person2 = (Person) person.clone();
System.out.println("person2原始数据:"+person2.getId()+"-"+person2.getInfo().getName()+"-"+person2.getInfo().getAge());
System.out.println("判断person与person2对象是否一样:"+(person==person2));//不一样成功拷贝。
System.out.println("判断person对象的Info值与person2对象的Info值是否一样:"+(person.info==person2.info));//一样,引用地址相同

// 修改person2的值
person2.setId(2);
person2.getInfo().setAge(60);
person2.getInfo().setName("Amy");

//查改修改后的对象的信息
System.out.println("person数据:"+person.getId() + "-" + person.getInfo().getName() + "-" + person.getInfo().getAge());
System.out.println("person2修改后的数据:"+person2.getId()+"-"+person2.getInfo().getName()+"-"+person2.getInfo().getAge());
//可以看到除了person的id不同外,其他均相同,person的info值被迫与person2修改的一致。
}
}

运行结果

1
2
3
4
5
6
person原始数据:1-Tom-20
person2原始数据:1-Tom-20
判断person与person2对象是否一样:false
判断person对象的Info值与person2对象的Info值是否一样:true
person数据:1-Amy-60
person2修改后的数据:2-Amy-60

从输出结果可以看出,一开始拷贝的person2与person的属性值都是一样的,但他们其实是不同的对象(内存中),通过第一个false和修改id可以判断,两个对象的id是分开独立的。而在判断两个对象的Info值是true,以及修改person2的name和age后,person的name和age也跟着改变了,说明两个对象的Info指向同一个对象(内存中)。由此可见,对象的浅拷贝拷贝的是对象的引用地址。

三、深拷贝方法及实例

深拷贝可以通过重写clone方法实现,也可以通过序列化实现。这里通过重写clone方法实现。

重写clone()

将上述的Person类的clone方法重写:

1
2
3
4
5
@Override
protected Object clone(){
// 深拷贝
return new Person(this.id, this.getInfo().getName(), this.getInfo().getAge());
}

运行结果

1
2
3
4
5
6
person原始数据:1-Tom-20
person2原始数据:1-Tom-20
判断person与person2对象是否一样:false
判断person与person2对象的Info属性是否一样:false
person数据:1-Tom-20
person2修改后的数据:2-Amy-60

从输出结果可以看出,一开始拷贝的person2与person的属性值都是一样的,但他们是属于不同的对象(内存中),二者之间互不相影响。

四、小结

本文分享了关于java深浅拷贝的相关知识,从平常的创建对象到拷贝,并深入认识了深浅拷贝的区别。浅拷贝,复制基本数据类型和对象的引用地址;深拷贝,复制基本数据类型和基于引用创建新的对象。最后通过Object方法的clone方法实例展示深浅拷贝。

认知扩展: java中只是值传递 引用拷贝拷贝的也是地址值。


参考文献

评论




博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 Volantis 作为主题,总访问量为

桂ICP备2021001128号