深拷贝和浅拷贝
在平时写代码的过程中创建对象一般都是用new关键字的方式来进行,通过拷贝方式创建对象一般运用在新的上下文需要复用对象部分数据或者全部数据的环境中。
拷贝方式分为浅拷贝、深拷贝和延迟拷贝。
浅拷贝
浅拷贝就是将对象中的所有基本数据类型进行一个拷贝,复制引用类型的指向地址,拷贝对象和被拷贝对象中的引用数据类型指向同一块内存空间,也就是说如果对该内存空间的值进行修改,那么这两个对象中的引用数据类型的值都会随之发生改变。
浅拷贝的实现
浅拷贝通过类实现Cloneable接口,通过重写clone方法来进行浅拷贝,通过代码对该方式进行一种实现。
public class CopyOne implements Cloneable{
private String name;
private int age;
private CopyTwo copyTwo;
public CopyOne(String name, int age) {
this.name = name;
this.age = age;
}
public CopyOne(String name, int age, CopyTwo copyTwo) {
this.name = name;
this.age = age;
this.copyTwo = copyTwo;
}
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 CopyTwo getCopyTwo() {
return copyTwo;
}
public void setCopyTwo(CopyTwo copyTwo) {
this.copyTwo = copyTwo;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CopyTwo{
private String name;
private int age;
public CopyTwo(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "CopyTwo{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
//测试类
public class Demo01 {
public static void main(String[] args) throws Exception{
CopyTwo copyTwo = new CopyTwo("小穗", 20);
CopyOne copyOne = new CopyOne("肖自在", 30, copyTwo);
CopyOne clone = (CopyOne)copyOne.clone();
System.out.println("被拷贝对象的基本数据=="+copyOne.getName());
System.out.println("拷贝对象的基本数据=="+clone.getName());
System.out.println("被拷贝对象的引用数据=="+copyOne.getCopyTwo());
System.out.println("拷贝对象的引用数据=="+clone.getCopyTwo());
//对被拷贝对象中的基本数据和引用数据进行修改,查看两者的一个变化
copyOne.setName("冯宝宝");
copyTwo.setName("小黑");
System.out.println("修改之后的被拷贝对象的基本数据=="+copyOne.getName());
System.out.println("修改之后的拷贝对象的基本数据=="+clone.getName());
System.out.println("修改之后的被拷贝对象的引用数据=="+copyOne.getCopyTwo());
System.out.println("修改之后的拷贝对象的引用数据=="+clone.getCopyTwo());
}
}
/*
结果:
被拷贝对象的基本数据==肖自在
拷贝对象的基本数据==肖自在
被拷贝对象的引用数据==CopyTwo{name='小穗', age=20}
拷贝对象的引用数据==CopyTwo{name='小穗', age=20}
修改之后的被拷贝对象的基本数据==冯宝宝
修改之后的拷贝对象的基本数据==肖自在
修改之后的被拷贝对象的引用数据==CopyTwo{name='小黑', age=20}
修改之后的拷贝对象的引用数据==CopyTwo{name='小黑', age=20}
*/
通过测试类的结果可以发现,浅拷贝是通过clone方法来进行克隆,clone方法内又是通过调用父类的clone方法来进行一个对象拷贝。当对被拷贝对象中的基本数据进行修改的时候被拷贝对象的基本数据不发生改变,如果对引用数据进行修改的话,拷贝对象和被拷贝对象都会发生改变。
深拷贝
深拷贝这种拷贝方式就是将浅拷贝中没有拷贝的引用数据也进行一个拷贝,也就是说通过深拷贝方式拷贝对象与被拷贝对象的属性的内存地址都不一样,都是独立的。深拷贝这种方式一般会使用在引用数据经常需要改变的场景中。
深拷贝的实现
深拷贝的实现有两种方式,一个是通过将对被拷贝对象中的引用数据进行一个拷贝,二是通过一个序列化的方式来进行拷贝
第一种方式代码实现
public class CopyOne implements Cloneable{
private String name;
private int age;
private CopyTwo copyTwo;
public CopyOne(String name, int age) {
this.name = name;
this.age = age;
}
public CopyOne(String name, int age, CopyTwo copyTwo) {
this.name = name;
this.age = age;
this.copyTwo = copyTwo;
}
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 CopyTwo getCopyTwo() {
return copyTwo;
}
public void setCopyTwo(CopyTwo copyTwo) {
this.copyTwo = copyTwo;
}
@Override
public Object clone() throws CloneNotSupportedException {
//首先获取父类的clone方法拷贝得出的对象
CopyOne clone = (CopyOne)super.clone();
//将引用数据的拷贝结果赋值给本类中的引用数据属性
clone.copyTwo=(CopyTwo) copyTwo.clone();
return clone;
}
}
//因为需要获取该类的拷贝对象,所以也需要实现Cloneable接口
public class CopyTwo implements Cloneable{
private String name;
private int age;
public CopyTwo(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "CopyTwo{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//测试类
public class Demo02 {
public static void main(String[] args) throws Exception{
CopyTwo copyTwo = new CopyTwo("小穗", 20);
CopyOne copyOne = new CopyOne("肖自在", 30, copyTwo);
CopyOne clone = (CopyOne)copyOne.clone();
System.out.println("被拷贝对象的基本数据=="+copyOne.getName());
System.out.println("拷贝对象的基本数据=="+clone.getName());
System.out.println("被拷贝对象的引用数据=="+copyOne.getCopyTwo());
System.out.println("拷贝对象的引用数据=="+clone.getCopyTwo());
//对被拷贝对象中的基本数据和引用数据进行修改,查看两者的一个变化
copyOne.setName("冯宝宝");
copyTwo.setName("小黑");
System.out.println("修改之后的被拷贝对象的基本数据=="+copyOne.getName());
System.out.println("修改之后的拷贝对象的基本数据=="+clone.getName());
System.out.println("修改之后的被拷贝对象的引用数据=="+copyOne.getCopyTwo());
System.out.println("修改之后的拷贝对象的引用数据=="+clone.getCopyTwo());
}
}
/*
结果:
被拷贝对象的基本数据==肖自在
拷贝对象的基本数据==肖自在
被拷贝对象的引用数据==CopyTwo{name='小穗', age=20}
拷贝对象的引用数据==CopyTwo{name='小穗', age=20}
修改之后的被拷贝对象的基本数据==冯宝宝
修改之后的拷贝对象的基本数据==肖自在
修改之后的被拷贝对象的引用数据==CopyTwo{name='小黑', age=20}
修改之后的拷贝对象的引用数据==CopyTwo{name='小穗', age=20}
*/
通过结果可以发现深拷贝实现的方式是通过将被拷贝对象中的引用数据也进行一个拷贝,然后赋值给自身的引用数据,这样子就实现了一个深拷贝。无论是对被拷贝对象中的基本数据或者引用数据进行修改,拷贝对象的数据都不会发生一个变化。 第二种方式
public class Rabbit implements Serializable {
private String name;
private int age;
public Rabbit() {
}
public Rabbit(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Rabbit{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
public class CopyThree implements Serializable {
private String name;
private int age;
private Rabbit rabbit;
public CopyThree(String name, int age, Rabbit rabbit) {
this.name = name;
this.age = age;
this.rabbit = rabbit;
}
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 Rabbit getRabbit() {
return rabbit;
}
public void setRabbit(Rabbit rabbit) {
this.rabbit = rabbit;
}
@Override
public String toString() {
return "CopyThree{" +
"name='" + name + ''' +
", age=" + age +
", rabbit=" + rabbit +
'}';
}
}
//测试类
public class Demo03 {
public static void main(String[] args) throws Exception{
Rabbit rabbit=new Rabbit("小白兔",1);
CopyThree clown = new CopyThree("小丑", 25, rabbit);
//通过序列化的方式来进行深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(clown);
os.flush();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bis);
CopyThree clone = (CopyThree)oi.readObject();
System.out.println("被拷贝对象=="+clown);
System.out.println("拷贝对象=="+clone);
//修改被拷贝对象中的基本数据和引用数据
clown.setAge(10);
rabbit.setAge(2);
System.out.println("修改之后的拷贝对象=="+clown);
System.out.println("修改之后的被拷贝对象=="+clone);
}
}
/*
结果:
被拷贝对象CopyThree{name='小丑', age=25, rabbit=Rabbit{name='小白兔', age=1}}
拷贝对象==CopyThree{name='小丑', age=25, rabbit=Rabbit{name='小白兔', age=1}}
修改之后的拷贝对象CopyThree{name='小丑', age=10, rabbit=Rabbit{name='小白兔', age=2}}
修改之后的被拷贝对象CopyThree{name='小丑', age=25, rabbit=Rabbit{name='小白兔', age=1}}
*/
通过结果发现序列化成功实现了对深拷贝的实现!但是序列化实现深拷贝不太推荐使用,由于序列化对象的时候会有进行IO操作,这样子性能比第一种差,并且使用序列化需要注意很多点,比如确保类能够进行序列化、被transient修饰的属性没有办法序列化也就没有办法进行拷贝等。
浅拷贝和深拷贝的区别
1、拷贝的范围不同。浅拷贝只会对基本数据类型进行一个拷贝,深拷贝则会对基本数据类型和引用数据类型进行一个拷贝。
2、实现方式不同。浅拷贝实现的方式是通过调用父类的clone方法进行一个浅拷贝的实现,而深拷贝则是首先需要对引用数据进行一个拷贝,然后将该引用数据拷贝之后的结果赋值给类中的引用数据,通过这种方式来实现深拷贝,所以深拷贝实现基于浅拷贝实现。深拷贝还能通过序列化的方式来进行实现
3、使用不同。浅拷贝被使用于引用数据不经常被修改的场景,而深拷贝则被使用于引用数据经常被修改的场景
延迟拷贝
延迟拷贝是浅拷贝和深拷贝的一个组合。一开始进行拷贝的时候会使用速度比较快的浅拷贝进行拷贝,在这其中会使用一个计数器来记录有多少对象使用该数据,当需要对对象中的数据进行修改的时候,会根据计数器来判断是否需要要共享数据从而使用深拷贝的来拷贝对象。
延迟拷贝从外界来看是一个深拷贝,由于内部有一个计数器,所以占用内存较高,并且在循环引用的过程中有可能造成一些问题