Java的深浅拷贝
Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。举例说明:比如,对象A和对象B都属于类S,具有属性a和b。那么对对象A进行拷贝操作赋值给对象B就是:B.a=A.a; B.b=A.b;
在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部 数据。
Java中的对象拷贝主要分为:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。
先介绍一点铺垫知识:Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。
浅拷贝(Shallow Copy):①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
package com.zhangqi.shallowcopy;
/**
* @author: zhangqi
* @create: 2022/1/2 14:06
*/
/* clone方法实现浅拷贝 */
public class ShallowCopy {
public static void main(String[] args) {
Age a = new Age(20);
Student stu1 = new Student("摇头耶稣", a, 175);
//通过调用重写后的clone方法进行浅拷贝
Student stu2 = (Student) stu1.clone();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("大傻子");
//改变age这个引用类型的成员变量的值
a.setAge(99);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(216);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age {
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return this.age + "";
}
}
/*
* 创建学生类
*/
class Student implements Cloneable {
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name, Age a, int length) {
this.name = name;
this.aage = a;
this.length = length;
}
//eclipe中alt+shift+s自动添加所有的set和get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage = age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length = length;
}
//设置输出的字符串形式
@Override
public String toString() {
return "姓名是: " + this.getName() + ", 年龄为: " + this.getaAge().toString() + ", 长度是: " + this.getLength();
}
//重写Object类的clone方法
@Override
public Object clone() {
Object obj = null;
//调用Object类的clone方法,返回一个Object实例
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
深拷贝:首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!
简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。
package com.zhangqi.deepcopy;
/**
* @author: zhangqi
* @create: 2022/1/2 14:10
*/
/* 层次调用clone方法实现深拷贝 */
public class DeepCopy {
public static void main(String[] args) {
Age a = new Age(20);
Student stu1 = new Student("摇头耶稣", a, 175);
//通过调用重写后的clone方法进行浅拷贝
Student stu2 = (Student) stu1.clone();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
System.out.println();
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("大傻子");
//改变age这个引用类型的成员变量的值
a.setAge(99);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(216);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age implements Cloneable {
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return this.age + "";
}
//重写Object的clone方法
@Override
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
/*
* 创建学生类
*/
class Student implements Cloneable {
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name, Age a, int length) {
this.name = name;
this.aage = a;
this.length = length;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage = age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "姓名是: " + this.getName() + ", 年龄为: " + this.getaAge().toString() + ", 长度是: " + this.getLength();
}
//重写Object类的clone方法
@Override
public Object clone() {
Object obj = null;
//调用Object类的clone方法——浅拷贝
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//调用Age类的clone方法进行深拷贝
//先将obj转化为学生类实例
Student stu = (Student) obj;
//学生类实例的Age对象属性,调用其clone方法进行拷贝
stu.aage = (Age) stu.getaAge().clone();
return obj;
}
}
//深拷贝 - 方式 2 通过对象的序列化实现 (推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Student copyObj = (Student) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
assert bos != null;
bos.close();
assert oos != null;
oos.close();
assert bis != null;
bis.close();
assert ois != null;
ois.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}