写在前面:
其实深拷贝和浅拷贝这个概念,不仅仅只是Java中独有的概念,很多地方都适用。目前网上的各种资料,对二者的描述我认为是模糊不清的,甚至有一些是错误的,简直是误人子弟!这篇博客主要是从源码角度总结一下浅拷贝和深拷贝的区别。
概述:
浅拷贝和深拷贝的概念都是针对对象而言的,且都是可以通过生成一个新的对象完成对原始对象的拷贝。那么区分的关键在什么地方:深、浅二字。
1.浅拷贝
浅拷贝的实现方法为重写继承制Object类的clone( )方法,见下:
protected native Object clone() throws CloneNotSupportedException;
想要实现浅拷贝,只需要让被拷贝类实现Cloneable( ) 接口,再进一步重写clone( ) 方法即可。见下:
public class test_copy implements Cloneable{
int age=3;
char sex='A';
TreeNode node=new TreeNode(4);
@Override
protected Object clone(){
try{
return super.clone();
}catch (CloneNotSupportedException c){
c.getStackTrace();
}
return null;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public TreeNode getNode() {
return node;
}
public void setNode(TreeNode node) {
this.node = node;
}
@Override
public String toString() {
return "test_copy{" +
"age=" + age +
", sex=" + sex +
", node=" + node +
'}';
}
}
我们来测试一下:
public static void main(String[] args){
test_copy test1 = new test_copy();
test_copy test2 = (test_copy)test1.clone();
System.out.println(test1.sex==test2.sex);
System.out.println(test1.age==test2.age);
System.out.println(test1.node==test2.node);
System.out.println(test1.hashCode());
System.out.println(test2.hashCode());
}
输出如下:
true
true
true
1639705018
1627674070
分析一波儿~可以看到在test1和test2的成员变量中,sex和age属于基本数据类型,基本数据类型在执行拷贝操作的时候会进行值的传递,所以test1和test2的sex和age全部相等是正确的。但是我们来看node这种引用数据类型,竟然test1和test2的node竟然也相等,问题出现了。这说明,对于node这种引用数据类型,浅拷贝仅仅将原变量的引用地址复制了一份,并未生成一个新的引用对象。换句话说,test1和test的node均指向了同一个TreeNode对象,复制的仅仅是引用地址,并未复制引用的原TreeNode对象。这就是浅拷贝!
2.深拷贝:
如果想要将node这种引用数据类型也完全创建一份新的拷贝,那就要用到深拷贝这个概念了。与浅拷贝一样,待拷贝的类也要实现Cloneable( )接口,且要重写clone( )方法,不同的是,要将成员变量的中的引用数据类型也单独的clone一边。那么自然就要让该引用数据类型也实现Cloneable( )接口,且也要重写Clone( )方法,见下:
public class TreeNode implements Cloneable{
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){
val = x;
}
@Override
protected Object clone(){
try {
return super.clone();
}catch (CloneNotSupportedException c){
c.getStackTrace();
}
return null;
}
}
然后再去重写test_copy的clone()方法如下:
public class test_copy implements Cloneable{
int age=3;
char sex='A';
TreeNode node=new TreeNode(4);
@Override
protected Object clone(){
try{
test_copy test = (test_copy)super.clone();
test.node = (TreeNode)this.node.clone();
return test;
}catch (CloneNotSupportedException c){
c.getStackTrace();
}
return null;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public TreeNode getNode() {
return node;
}
public void setNode(TreeNode node) {
this.node = node;
}
@Override
public String toString() {
return "test_copy{" +
"age=" + age +
", sex=" + sex +
", node=" + node +
'}';
}
}
我们来测试一下:
public static void main(String[] args){
test_copy test1 = new test_copy();
test_copy test2 = (test_copy)test1.clone();
System.out.println(test1.sex==test2.sex);
System.out.println(test1.age==test2.age);
System.out.println(test1.node==test2.node);
System.out.println(test1.hashCode());
System.out.println(test2.hashCode());
}
输出结果为:
true
true
false
1639705018
1627674070
可以明显看到,test1和test2的node成员变量为完全不同的两个对象,不再仅仅是指向同一个对象,从而实现了深拷贝。而深拷贝还有另外一种实现方式,那就是序列化(见我的另外一篇博文)。
总结:
浅拷贝和深拷贝就是在基本数据类型和引用数据类型的基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。