Java中深拷贝和浅拷贝的研究

本文探讨了Java中的深拷贝和浅拷贝,详细解释了两者的概念,以及如何通过clone()方法实现对象拷贝。通过示例展示了只有基本数据类型的对象拷贝是深拷贝,而包含引用类型的对象拷贝则是浅拷贝。通过让引用类型的类实现Cloneable接口并覆盖clone()方法,可以实现包含引用类型的深拷贝。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中深拷贝和浅拷贝的研究

以这个问题开篇吧:String类型的拷贝到底是浅拷贝还是深拷贝???

准备说写一个程序来测试下,发现String没有实现Cloneable接口,也没有重写clone方法

String str1="wuanghao";
//String str2=str1.clone();//报错
String str3=str1;//复制引用

String不是基本数据类型,String也没有实现Cloneable接口,也就是说只能复制引用。

那么在修改克隆之后的对象str3之后,会不会将原来的值str1也改变了?答案是不会改变

public class StringCopy {
    public static void main(String [] args){
        String str1="wuanghao";
        //String str2=str1.clone();
        String str2=str1;
        str2=str2+"wo";
        System.out.println(str1);
    }
}

用以上代码测试,对象str1是没有被改变的。

原因或许大家都知道
因为String是在内存中不可以被改变的对象,就比如说在for大量循环中不推荐使用+的方式来拼凑字符串一样,每次使用+都会新分配一块内存,不在原来上修改,原来的没有指向它的引用,会被回收。所以克隆相当于1个String内存空间有两个引用,当修改其中的一个值的时候,会新分配一块内存用来保存新的值,这个引用指向新的内存空间,原来的String因为还存在指向他的引用,所以不会被回收,这样,虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值。

就是因为:虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值
所以在很多情况下,我们可以把String在clone的时候和基本类型做相同的处理,只是在equal时注意一些就行了。

这样关于上面问题的答案就是:String类型是浅拷贝,但是一般把它当深拷贝对待。

1.浅拷贝与深拷贝概念

(1)浅拷贝(浅克隆)

浅拷贝又叫浅复制,将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段(java中8中原始类型)的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的还是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。
浅拷贝简单归纳就是只复制一个对象,对象内部存在指向其他对象,数组或引用则不复制。

(2)深拷贝(深克隆)

将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。
深拷贝简单归纳就是对象内部引用的对象均复制。

2.Java中的clone()方法

(1)clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足下面规范:
①对任何的对象x,都有x.clone() != x;//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()== x.getClass();//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x);应该成立。
(2)Java中对象的克隆
clone()方法是在Object中定义的,而且是protected的,只有实现了Cloneable接口的类才可以在其实例上调用clone()方法,否则会抛出CloneNotSupportException。
为了获取对象的一份拷贝,我们可以利用Object类的clone()方法,也可以实现Cloneable接口,覆盖基类的clone()方法,在clone()方法中,调用super.clone()。
Cloneable接口是一个标记接口,也就是没有任何内容,定义如下:

package java.lang;
pubilc interface Cloneable{
}

例子1:对象中只含基本数据类型(完成的是深拷贝)

Student类:只含基本数据类型和Sting类型

package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
    private String name;
    private int age;
    //将Object中的Protected换成public
    @Override
    public Object clone() {
        Student s=null;
        try {
            s=(Student)super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //s.professor=(Professor) professor.clone();//报错
        return s;
    }
    // 。。。。这里还有各个属性的getter和setter方法
}

测试类

public class TestStudent {

    public static void main(String[] args) {
        Student s1=new Student("wuranghao", 20);
        Student s2=(Student)s1.clone();
        //改变s2之前s1的各个属性的值如下:
        System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName());
        //修改对象s2,观察对象s1是否改变
        s2.setAge(18);
        s2.setName("好哥");
        System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+);

    }
}

观察运行结果可以看到:修改对象s2后,对象s1的值是没有改变的。
即这种情况完成的是深拷贝。

例子:对象中含有引用类型(完成的是浅拷贝)

还是以上面的学生类为例,只是还添加了一个Professor类,学生类中有一个Professor的引用。

代码如下:

Professor类

package com.wrh.copy;

public class Professor {
    private String name;
    private int  age;
    public Professor(String name, int age) {
        super();
        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;
    }


}

Student类:含有引用类型Professor

package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
    private String name;
    private int age;
    private Professor professor;
    //将Object中的Protected换成public
    @Override
    public Object clone() {
        Student s=null;
        try {
            s=(Student)super.clone();
        } catch (CloneNotSupportedException e) {

            e.printStackTrace();
        }
        //s.professor=(Professor) professor.clone();//报错
        return s;
    }
    public Student(String name, int age, Professor professor) {
        super();
        this.name = name;
        this.age = age;
        this.professor = professor;
    }
    // 。。。。这里还有各个属性的getter和setter方法

}
package com.wrh.copy;

public class TestStudent {

    public static void main(String[] args) {
        Professor professor=new Professor("professorA",30);
        Student s1=new Student("wuranghao", 20,professor);
        Student s2=(Student)s1.clone();
        //改变s2之前s1的各个属性的值如下:
        System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
        //修改对象s2,观察对象s1是否改变
        s2.setAge(18);
        s2.setName("好哥");
        s2.getProfessor().setName("professorB");
        s2.getProfessor().setAge(50);
        System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());

    }

}

运行结果
改变s2之前s1的各个属性的值如下:20 wuranghao 30 professorA
改变s2之后s1的各个属性的值如下:20 wuranghao 50 professorB

同结果可以看出,Student 对象的拷贝,其对象中的基本数据类型和String类型发生了深拷贝,而对象中的引用类型发生了浅拷贝。

如何做到含有引用类型的对象完成深拷贝呢

其实很简单
方法:将引用类型的类也实现Cloneable接口,并将Object中clone()方法的权限Protected换成public即可。然后在需要克隆的类中的clone方法中加上一行克隆这个引用的代码即可。例如

//其中professor为s对象的一个引用
s.professor=(Professor)this.professor.clone();

具体如下:
Professor类:实现了Cloneable接口,也扩大了clone方法的权限。

package com.wrh.copy;

public class Professor implements Cloneable{
    private String name;
    private int  age;
    public Professor(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public Object clone()  {
        Professor p=null;
        try {
            p=(Professor)super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return p;
    }
    // 。。。。这里还有各个属性的getter和setter方法
}

Student类:含有Professor引用。在clone方法中加上了
s.professor=(Professor)this.professor.clone();代码,即使得引用类型完成深拷贝。如果不加上这行代码,则还是完成的是浅拷贝(废话,是吧)。

package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
    private String name;
    private int age;
    private Professor professor;
    //将Object中的Protected换成public
    @Override
    public Object clone() {
        Student s=null;
        try {
            s=(Student)super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        s.professor=(Professor)this.professor.clone();//Professor实现了Clonable并重写了clone方法,这样就实现了深拷贝
        return s;
    }
    public Student(String name, int age, Professor professor) {
        super();
        this.name = name;
        this.age = age;
        this.professor = professor;
    }
    // 。。。。这里还有各个属性的getter和setter方法
}

测试类:与上面的测试类一模一样。

package com.wrh.copy;

public class TestStudent {

    public static void main(String[] args) {
        Professor professor=new Professor("professorA",30);
        Student s1=new Student("wuranghao", 20,professor);
        Student s2=(Student)s1.clone();
        //改变s2之前s1的各个属性的值如下:
        System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
        //修改对象s2,观察对象s1是否改变
        s2.setAge(18);
        s2.setName("好哥");
        s2.getProfessor().setName("professorB");
        s2.getProfessor().setAge(50);
        System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());

    }

}

运行结果如下:
改变s2之前s1的各个属性的值如下:20 wuranghao 30 professorA
改变s2之后s1的各个属性的值如下:20 wuranghao 30 professorA

即对象的引用类型也完成的是深拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值