浅拷贝
概述:
- 对于数据类型是基本数据类型和String的成员变量,浅拷贝会直接进行值传递。即对此类型的成员变量值进行修改,不会影响另一个对象拷贝得到的数据
- 对于数据类型是 引用数据类型 的成员变量,浅拷贝会进行引用传递。即在一个对象中修改成员变量会影响到另一个对象的成员变量值
如果不进行特殊处理,引用数据类型的拷贝都是浅拷贝
浅拷贝的实现:
以前记得笔记,可以不看,看了反而会晕
例程类
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 Student /* implements Cloneable */{ String id; Age age; public Student(){
} public Student(Student student){ this.id = student.id; this.age = student.age; } public Student(String id,Age age){ this.id = id; this.age = age; }
}
|
1 2 3 4 5 6 7 8 9 10 11
| public class Age { int ageNum;
public Age() {
}
public Age(int age) { this.ageNum = age; } }
|
普通方法实现浅拷贝
通过赋值的方式实现浅拷贝。(构造方法浅拷贝实际上也是一种赋值,只不过其赋值是在类的构造函数中实现的)
1 2 3 4 5 6 7 8 9
| Student s1 = new Student("12",new Age(20)); Student s2 = new Student(s1); Student s3 = s1;
s2.id = "21";s2.age.ageNum = 10;
s3.id = "21";s3.age.ageNum = 30;
|
为什么呢?
- s3是直接由s1赋值得到的,整个s3都是对s1的浅拷贝
- s2是通过构造函数得到的,而Student的构造函数是分别对
id和age进行赋值,其中age是引用数据类型。所以s2是对s1的age进行了浅拷贝。
通过重写clone()方法实现浅拷贝
只对当前类中的引用数据类型进行浅拷贝,类似于普通浅拷贝中的构造函数拷贝方式。
当然,只局限于当前类,也就是说,Student -> age -> ageNum 虽是基本数据类型,但由于在Student中的是age不是ageNum,所以只会对age进行浅拷贝。
快捷实现:
- 给要拷贝的类实现Cloneable接口
- 在该类重写clone(),clone()里面调用父类 — Object 的clone()。在IDEA中可以通过快捷键
alt+ins直接生成
1 2 3 4 5 6 7 8 9
| Student s1 = new Student("12",new Age(20)); Student s2 = null; try { s2 = (Student) s1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } s2.id = "21";s2.age.ageNum = 10;
|
在Student中,id是int,值传递,不变;age是Age,引用传递,变);
深拷贝
概述
- 简单地说,深拷贝对引用数据类型的成员变量中
所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建新的内存空间。 - 深拷贝和浅拷贝的区别体现在引用数据类型上
- 经过深拷贝后,引用数据类型的值不会随拷贝的值的变化而变化,相互独立
实现深拷贝
通过多次重写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
| public class Student implements Cloneable { String id; Age age;
public Student() {
}
public Student(Student student) { this.id = student.id; this.age = student.age; }
public Student(String id, Age age) { this.id = id; this.age = age; }
@Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Student s = (Student) obj; s.age = (Age) s.age.clone(); s.age.birthdayYear = (BirthdayYear) s.age.birthdayYear.clone(); return obj; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Age implements Cloneable { int ageNum; BirthdayYear birthdayYear;
public Age() {
}
public Age(int age, BirthdayYear birthdayYear) { this.ageNum = age; this.birthdayYear = birthdayYear; }
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class BirthdayYear implements Cloneable { int year;
public BirthdayYear() {
}
public BirthdayYear(int year) { this.year = year; }
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
具体步骤
对每一层 每一个引用数据对象都使用浅拷贝以实现深拷贝
- 每个类都实现Cloneable接口
- 每个类都重写clone()方法。除最顶层类外,都返回父类的clone(),即 super.clone()
在最顶层类重写的clone()方法中 调用其内层的所有clone()方法
总结以上两点,可以得到此方法实现思路:
深拷贝=对每一层 每一个引用数据对象都进行一次浅拷贝
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) throws CloneNotSupportedException { Student s1 = new Student("21", new Age(20, new BirthdayYear(2000))); Student s2 = (Student) s1.clone();
s2.id = 12; s2.age.ageNum = 10; s2.age.birthdayYear.year = 2020; }
|
由此可以看出,经过深拷贝后s1中引用数据类型的值不会随s2的值的变化而变化
通过对象序列化实现深拷贝
为什么要用序列化实现深拷贝?
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。
总而言之:一层一层的实现浅拷贝,对应层级关系小的类还好说,但是一旦层级关系深的话,就太累啦!
关于Java序列化,可以参考以下文章
https://www.cnblogs.com/9dragon/p/10901448.html
https://www.jianshu.com/p/bee1c29c366e
例程类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Student implements Serializable {
String name; Age age; int length;
public Student(String name, Age age, int length) { this.name = name; this.age = age; this.length = length; }
public String toString() { return "姓名是: " + name + ", 年龄为: " + age.toString() + ", 高度是: " + length; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Age implements Serializable { int ageNum;
public Age(int ageNum) { this.ageNum = ageNum; }
public String toString() { return ageNum +""; } }
|
具体步骤
- 将每一个引用数据类型都实现
Serializable接口 - 具体实现可以调用自拟的
Object DeepCopyBySerializable(T t) 方法
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
| public static <T> Object DeepCopyBySerializable(T t) { ObjectOutputStream oos = null; ObjectInputStream ois = null; Object obj = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { oos = new ObjectOutputStream(bos); oos.writeObject(t); oos.flush(); ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); obj = ois.readObject(); oos.close(); bos.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return obj; }
public static void main(String[] args) throws IOException, ClassNotFoundException { Student s1 = new Student("Jack", new Age(20), 175);
Student s2 = (Student) DeepCopyBySerializable(s1);
s2.name = "大傻子"; s2.length = 185; s2.age.ageNum = 25;
}
|
可以看出,成功实现了深拷贝。
这是因为此深拷贝由序列化和反序列化实现,具体改变是对字符串进行操作的。
2种方法的区别
- 多次clone():可读性强,但繁琐;序列化:可读性差,但简洁
- 多次clone():主要操作在
需要被拷贝的类里;序列化:主要操作在main方法或自拟方法中
小结
上次对Java深拷贝的学习只是匆匆扫过,知道个大概。今天重新将其整理一遍,在对知识点复习的同时,也算让我对其的理解更深刻了吧,对我当时笔记中的一些不合理部分也进行了删除和更改。深拷贝的知识以目前的我来说还不是很用的到,希望下次的复习能让我更有收获和感悟。