java中的浅拷贝和深拷贝解析
任何编程语言中,其实都有浅拷贝和深拷贝的概念,java中也不例外。在对一个现有的对象进行拷贝操作的时候,是有浅拷贝和深拷贝之分的,他们在实际使用中,区别很大,如果对其进行混淆,可能会引发一些难以排查的问题。
本文就在java中的浅拷贝和深拷贝做一个详细的解说。
什么是浅拷贝和深拷贝?
首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。
在java中,除了基本数据类型(元类型)之外,还存在类的实例对象这个引用数据类型。而一般使用【=】号做赋值操作的时候。对于基本数据类型,实际上拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向同一个对象。
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
所以到现在,就应该了解了,所谓浅拷贝和深拷贝,只是在拷贝对象的时候,对类的实例对象这种引用数据类型的不同操作而已。
总结来说:
1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递的拷贝,此为浅拷贝。
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
从内存角度区分浅拷贝和深拷贝的区别
- 数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。
- 浅拷贝:对于基本数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
- 深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象的值。
- 深拷贝相比较于浅拷贝速度较慢并且花销较大。
浅拷贝和深拷贝案例分析
- 浅拷贝实现Cloneable,深拷贝是通过实现Serializable读取二进制流
深拷贝实现
首先Person对象实现Serializable接口,然后自定义深拷贝方法deepClone()
/**
* 深拷贝
* @return Person 注意 要实现序列化接口
*/
public Person deepClone(){
try {
//输出(序列化)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//输入(反序列化)
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bais);
Person person=(Person) ois.readObject();
return person;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
接下来验证一下深拷贝是否成功:
@Test
public void testProtoType(){
Person person1=new Person();
person1.setUsername("wcc");
person1.setAge(20);
//初始化list 并为其加入数据
person1.setList(new ArrayList<>());
person1.getList().add("aaa");
person1.getList().add("bbb");
System.out.println(person1);
//深拷贝
Person person=person1.deepClone();
person.setUsername("hzw");
//给person中的list添加一条数据
person.getList().add("ccc");
System.out.println("person:"+person);
System.out.println("person1:"+person1);
boolean flag1=person1==person;
System.out.println("person1和person的引用地址是否相同:"+flag1);
boolean flag2=person1.getList()==person.getList();
System.out.println("person1和person的list引用地址是否相同:"+flag2);
}
输出结果如下:
Person{username='wcc', age=20, list=[aaa, bbb]}
person:Person{username='hzw', age=20, list=[aaa, bbb, ccc]}
person1:Person{username='wcc', age=20, list=[aaa, bbb]}
person1和person的引用地址是否相同:false
person1和person的list引用地址是否相同:false
由结果可得出:深拷贝person所得到的list内存地址和原来的person1中的内存地址都是不同的,深拷贝成功。