java中的浅复制和深复制

Java深浅复制详解
本文深入探讨Java中对象复制的两种方式:浅复制与深复制。解析Object类的clone方法及其实现,介绍序列化接口在深复制中的应用,演示通过字节流实现深复制的具体操作。

编程的艺术 ----Ctrl + C , Ctrl + V 在代码中的体现

前言 : 你是否在写代码的时候遇见过一个对象要new多个一样的或者改动不大的 复制粘贴使得代码很难看不雅观而身有体会呢?
那么这篇文章绝对适合你 它还存在一个设计模式–原型模式


浅复制

先介绍一下浅复制

浅复制是指当对象的字段值被复制时,字段引用的对象不会被复制 只会得到其引用 例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅复制,那么两个对象将引用同一个字符串·

那么浅复制怎么实现呢?

其实在Object类中有一个方法 Object clone()
这个方法就可以完成浅复制 只需要子类重写这个方法 并且实现了Cloneable接口即可
例如一个Teacher类 和 Student类

public class Teacher {
    private String name;

    public Teacher(String name) {
        this.name = name;
    }

    public Teacher() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                '}';
    }
}

public class Student implements Cloneable {
    private int age;
    private Teacher teacher;

    public Student(int age, Teacher teacher) {
        this.age = age;
        this.teacher = teacher;
    }

    public Student() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", teacher=" + teacher +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object object = super.clone();
        return object;
    }
}

可能有人就想问了 clone方法既然是Object 的 那么 你实现了Cloneable接口难道不用重写其方法
那么让我们看一下Cloneable的源码


public interface Cloneable {
}

对的 这个接口的源码就是这个样子 其实他只是一个标识符
你实现了这个接口表示你支持复制 不实现代表不复制

接下来看一下测试类

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
       Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.clone();

        System.out.println(stu1);
        System.out.println(stu2);

    }
}

在这里插入图片描述
这样做的好处是什么呢?
比如一个类 有很多成员变量 你再给他set值的时候会set很多行
然后你又需要很多个一样的对象 这个时候你是不是就想到了复制粘贴
导致 代码几百行 而且都是重复的 很难看
用这种方法是不是就不用那么麻烦了
还有一个好处就是不用去初始化 万一你的构造器很复杂 很消耗时间
如果用我们的方法就

但是! 这里却有个缺陷 还记得我们之前说的如果对象类型只会得到其引用
接下来让我们看看这个事情 修改其测试类代码

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.clone();

        System.out.println(stu1);
        System.out.println(stu2);

        stu2.getTeacher().setName("唐嫣");

        System.out.println(stu1);
        System.out.println(stu2);
    }
}

然后看一下输出结果

在这里插入图片描述

我们只是修改了stu2的老师的名字 stu1的也跟着变了
所以说浅复制只是获得了其引用可能有人还是不相信 看一下这个测试代码

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.clone();
        System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
    }
}

输出结果
在这里插入图片描述
两个对象是同一个 这就造成了很多复制上的弊端
让我们来看一下深复制


深复制

深复制会赋值所有的属性,并复制属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深赋值贝。深复制相比于浅复制速度较慢并且花销较大。

我这里讲几种深复制的方法

1.构造深复制法

@Override
    protected Object clone() throws CloneNotSupportedException {
        Teacher teacher = new Teacher(this.teacher.getName());
        Student student = new Student(this.age, teacher);
        return student;
    }

修改复制的方法可能比较笨
然后再看测试类

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.clone();

        System.out.println(stu1);
        System.out.println(stu2);

        stu2.getTeacher().setName("唐嫣");

        System.out.println(stu1);
        System.out.println(stu2);

        System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
    }
}

在这里插入图片描述

这次就不是同一个对象了.还有一种方法就是实现序列化接口

序列化接口: Serializable接口
序列化: 就是把一个对象的状态全部用字节码保存起来 存到硬盘 数据库等等…
反序列化:就是序列化的逆过程 把你存起来的字节码恢复成对象 其底层依赖反射

在Teacher 类 和Student类中实现Serializable接口
并尝试着把Student对象写到文件里
在Student里实现一个方法

public void write() throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
        objectOutputStream.writeObject(this);

    }

当然文件路径你可以自定义 也可以不写到文件里 随便你

然后测试类

import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        Student student = new Student(18, new Teacher("刘亦菲"));
        student.write();
    }
}

然后再用同样的方法读出来

public static Object read() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
        return objectInputStream.readObject();
    }

修改测试类

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student(18,new Teacher("刘亦菲"));
        System.out.println(student);
        student.write();
        Student student1 = read();
        System.out.println(student1);
    }
    public static Student read() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
        return (Student) objectInputStream.readObject();
    }
}

在这里插入图片描述
输出结果 让我们看一下两个对象是不是同一个

修改测试类代码

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student(18,new Teacher("刘亦菲"));
        System.out.println(student);
        student.write();
        Student student1 = read();
        System.out.println(student1);
        System.out.println(student.equals(student1));
        System.out.println(student1.getTeacher().equals(student.getTeacher()));

    }
    public static Student read() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
        return (Student) objectInputStream.readObject();
    }
}

在这里插入图片描述
这里其实就是两个不同的对象了

这里还需要强调一个字段
serialVersionUID

serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

如果你需要在两个不同的进程一个写 一个读的话就需要这个东西的显示定义 也就是你自己加一个字段
于是修改了Student类的定义

import java.io.*;

public class Student implements Cloneable,Serializable {
    private int age;
    private Teacher teacher;
    private static final long serialVersionUid = 1L;

    public Student(int age, Teacher teacher) {
        this.age = age;
        this.teacher = teacher;
    }

    public Student() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", teacher=" + teacher +
                '}';
    }

    //浅复制
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void write() throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
        objectOutputStream.writeObject(this);

    }



}

代码的第六行 定义了 private static final long serialVersionUid = 1L;

好了 然后怎么进行深复制你们应该明白了 其实最好的方法不要写到硬盘中 而是写到流中
比如

  //深复制
    public Object deepClone() throws IOException, ClassNotFoundException {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);
        
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();

    }

学生类代码

import java.io.*;

public class Student implements Cloneable,Serializable {
    private int age;
    private Teacher teacher;
    private static final long serialVersionUid = 1L;

    public Student(int age, Teacher teacher) {
        this.age = age;
        this.teacher = teacher;
    }

    public Student() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", teacher=" + teacher +
                '}';
    }

    //浅复制
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }



    //深复制
    public Object deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }
}

我们修改测试类

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.deepClone();

        System.out.println(stu1);
        System.out.println(stu2);

        stu2.getTeacher().setName("唐嫣");

        System.out.println(stu1);
        System.out.println(stu2);

        System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
    }
}

在这里插入图片描述
输出结果

好了 贴一下详细的代码 首先 Teacher类

import java.io.Serializable;

public class Teacher implements Serializable {
    private String name;

    public Teacher(String name) {
        this.name = name;
    }

    public Teacher() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                '}';
    }
}

然后Student类

import java.io.*;

public class Student implements Cloneable,Serializable {
    private int age;
    private Teacher teacher;
    private static final long serialVersionUid = 1L;

    public Student(int age, Teacher teacher) {
        this.age = age;
        this.teacher = teacher;
    }

    public Student() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", teacher=" + teacher +
                '}';
    }

    //浅复制
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void write() throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
        objectOutputStream.writeObject(this);

    }

    public Object read() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
        return objectInputStream.readObject();
    }


    //深复制
    public Object deepClone() throws IOException, ClassNotFoundException {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();

    }

}

测试类

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Teacher teacher = new Teacher("刘亦菲");
        Student stu1 = new Student(18,teacher);
        Student stu2 = (Student) stu1.deepClone();

        System.out.println(stu1);
        System.out.println(stu2);

        stu2.getTeacher().setName("唐嫣");

        System.out.println(stu1);
        System.out.println(stu2);

        System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
    }
}
<think>好的,我现在需要回答用户关于Java浅复制深复制的区别及实现方式的问题。首先,我得回忆一下自己对这些概念的理解,然后结合提供的引用资料来确保回答的准确性。浅复制深复制的核心区别在于对象引用的处理。浅复制只复制对象本身,而不复制其引用的其他对象,这意味着原对象副本会共享这些引用对象。而深复制则会递归复制所有引用的对象,因此副本原对象完全独立,修改其中一个不会影响另一个。接下来,我需要查看用户提供的引用资料,看看有没有具体的实现方式或者例子。引用[1]提到浅拷贝深拷贝的定义,引用[2][4]展示了通过重写clone方法实现深复制的例子,其中需要手动复制嵌套对象。引用[3]则介绍了使用序列化的方法来实现深复制,这种方法需要所有相关类都实现Serializable接口。用户可能希望了解不同的实现方法,比如使用clone方法序列化方法之间的区别,以及各自的优缺点。此外,用户可能还想知道在实际开发中如何选择合适的方法,或者常见的陷阱有哪些,比如未正确实现Cloneable接口导致异常,或者忘记处理嵌套对象的复制。 在组织回答时,应该先明确概念的区别,然后分点说明实现方式,每种方式的具体步骤,并附上代码示例。同时,需要指出注意事项,比如深复制需要所有涉及的对象都支持复制操作,否则可能导致不完全的复制。另外,用户提供的引用中有一些代码示例,比如引用[2]中的Grade类clone方法,引用[4]中的Student类clone方法,以及引用[3]中的序列化方法。这些都可以作为例子来帮助说明不同的实现方式。 需要注意的是,Javaclone方法默认是浅复制深复制需要手动处理。而序列化方法则是一种替代方案,可能更适用于复杂对象结构的深复制,但性能可能较低。此外,还要提到深复制的常见问题,比如循环引用如何处理,或者是否所有对象都可序列化。最后,生成相关问题时要围绕用户可能感兴趣的扩展点,比如性能比较、应用场景、其他实现方式(如使用第三方库)等。</think>### Java浅复制深复制的区别及实现方式 #### 一、核心概念 1. **浅复制(Shallow Copy)** - 仅复制对象本身,不复制其内部引用的其他对象 - 原对象与副本共享引用类型成员变量 - 示例代码中的$Student$类默认实现[^4]: ```java @Override public Object clone() { return super.clone(); //仅复制基本类型字段 } ``` 2. **深复制(Deep Copy)** - 递归复制所有引用对象,实现完全独立的数据副本 - 修改副本不会影响原对象 - 如引用[2]中$Grade$类的实现: ```java @Override public Object clone() { Grade cloned = (Grade)super.clone(); cloned.headTeacher = (Teacher)this.headTeacher.clone(); //显式复制引用对象 return cloned; } ``` #### 二、实现方式对比 | **特性** | 浅复制 | 深复制 | |----------------|-------------------------------|-------------------------------| | 对象独立性 | 引用类型字段共享 | 所有字段完全独立 | | 实现复杂度 | 简单(默认clone方法) | 需手动处理嵌套对象 | | 性能消耗 | 低 | 高(尤其对象层级深时) | | 典型应用场景 | 简单无嵌套对象 | 含多层嵌套引用的复杂对象 | #### 三、具体实现方法 **1. 重写clone方法** - 浅复制示例(引用[4]): ```java class Student implements Cloneable { @Override public Object clone() { return super.clone(); //自动复制基本类型字段 } } ``` - 深复制实现关键(引用[2]): ```java class Grade implements Cloneable { Teacher headTeacher; @Override public Object clone() { Grade cloned = (Grade)super.clone(); cloned.headTeacher = (Teacher)headTeacher.clone(); //递归复制 return cloned; } } ``` **2. 序列化实现深复制**(引用[3]) ```java //通过对象流实现深度克隆 public static <T extends Serializable> T clone(T obj) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (T) ois.readObject(); } catch (Exception e) { return null; } } ``` *要求所有嵌套对象实现$Serializable$接口* #### 四、注意事项 1. **Cloneable接口陷阱** - 不实现$Cloneable$接口调用$clone()$会抛出$CloneNotSupportedException$ - 深复制需确保所有嵌套类都支持复制操作 2. **性能权衡** - 序列化方式比递归clone方法更耗资源 - 对不可变对象(如String)无需深复制 3. **第三方工具** - Apache Commons Lang的$SerializationUtils$ - Gson/Jackson通过JSON序列化实现深复制
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值