一、引言
对象的拷贝即将一个对象的属性拷贝到另一个相同类型的对象中去。应用的场景大多数在一个新的上下文环境中需要复用被拷贝对象的部分或者全部数据。在Java中拷贝的方式有三:浅拷贝、深拷贝、延迟拷贝。
二、浅拷贝
2.1 什么是浅拷贝
浅拷贝是按位拷贝对象,会创建一个对象,其有这原始对象的精确拷贝。如果属性是基本类型,那么拷贝的就是基本类型的值;如果属性是内存地址,那么拷贝的就是内存地址,如果其中一个改变了该地址,那么就会影响到另一个。
2.2 浅拷贝代码实现
2.2.1 创建浅拷贝对象
public class Subject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Subject(String name) {
this.name = name;
}
@Override
public String toString() {
return "Subject{" +
"name='" + name + '\'' +
'}';
}
}
// 被浅拷贝的对象
public class ShallowCopyStudent implements Cloneable {
// 基本数据类型
private String name;
// 对象(即内存地址)
private Subject subject;
public ShallowCopyStudent(String name, String subject) {
this.name = name;
this.subject = new Subject(subject);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
@Override
public String toString() {
return "ShallowCopyStudent{" +
"name='" + name + '\'' +
", subject=" + subject +
'}';
}
// 浅拷贝关键方法,直接调用父类的clone()
@Override
protected Object clone() {
try {
return super.clone();
} catch (Exception e) {
return null;
}
}
}
2.2.2 修改浅拷贝对象信息
// 浅拷贝实例代码
private static void testShallowCopy() {
// 原始对象
ShallowCopyStudent originShallowStudent = new ShallowCopyStudent("android", "java");
System.out.println("原始对象: " + originShallowStudent.toString());
// 浅拷贝对象
ShallowCopyStudent copyShallowStudent = (ShallowCopyStudent) originShallowStudent.clone();
System.out.println("浅拷贝对象: " + copyShallowStudent);
// 修改浅拷贝对象信息
copyShallowStudent.setName("google");
copyShallowStudent.getSubject().setName("kotlin");
System.out.println("修改后原始对象: " + originShallowStudent);
System.out.println("修改后浅拷贝对象: " + copyShallowStudent);
}
2.2.3 输出结果:
原始对象: ShallowCopyStudent{name='android', subject=Subject{name='java'}}
浅拷贝对象: ShallowCopyStudent{name='android', subject=Subject{name='java'}}
修改后原始对象: ShallowCopyStudent{name='android', subject=Subject{name='kotlin'}}
修改后浅拷贝对象: ShallowCopyStudent{name='google', subject=Subject{name='kotlin'}}
三、深拷贝
3.1 什么是深拷贝
深拷贝,即拷贝的对象将原始对象的数据拷贝。基本数据类型及引用类型的值都会被拷贝一份,且深拷贝的对象的修改不影响原始对象。
3.2 深拷贝代码实现
3.2.1 核心代码实现
基于2.2浅拷贝代码实现做修改,深拷贝实现如下:
public class StudentDeepCopy implements Cloneable {
private String name;
private Subject subject;
public StudentDeepCopy(String name, String subject) {
this.name = name;
this.subject = new Subject(subject);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
@Override
public String toString() {
return "StudentDeepCopy{" +
"name='" + name + '\'' +
", subject=" + subject +
'}';
}
@Override
protected Object clone() {
// 深拷贝代码关键代码,创建新的对象
try {
return new DeepCopyStudent(name, subject.getName());
} catch (Exception e) {
return null;
}
}
}
3.2.2 输出结果
原始对象: StudentDeepCopy{name='android', subject=Subject{name='java'}}
深拷贝对象: StudentDeepCopy{name='android', subject=Subject{name='java'}}
修改后原始对象: StudentDeepCopy{name='android', subject=Subject{name='java'}}
修改后深拷贝对象: StudentDeepCopy{name='google', subject=Subject{name='kotlin'}}
3.3 通过序列化实现深拷贝
通过序列化实现深拷贝,将整个对象写入到一个持久化存储文件中并在需要时读取回来,此时的读取的对象其实就是对整个对象的拷贝。需要注意的是,在通过序列化进行深拷贝时,需保证对象中的所有属性都是可序列化的,即确保没有transient关键字修改。
除此之外,还需要注意性能问题,创建socket传输序列化对象,随后反序列,这个过程与直接调用已有对象相比是存在很大性能差别。因此如果对性能有要求的话,建议通过实现Clonable接口进行深拷贝实现。
四、延迟拷贝
延迟拷贝是深拷贝与浅拷贝的组合,采用一个计数器来记录有多少对象共享数据,当程序要修改原始对象时,会根据该数据是否被共享并根据需要来进行拷贝。
当原始对象中引用不经常改变的时候可以使用延迟拷贝,但由于存在计数器,效率下降很高,且循环引用可能会导致一些问题。
五、如何选择使用
当拷贝的对象只改变基本数据类型的时候,使用浅拷贝,但如果有引用类型的时候,采用深拷贝。因此根据实际需要,判断对象的引用类型是否经常被改变,若纹丝不变,则使用浅拷贝即可。