Java深拷贝详细总结

浅拷贝

直接看例子

package domain;

public class Subject {
    private String name;
    private int score;

    public Subject(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
package domain;

public class Student implements Cloneable {
    //基础类型和String不可变对象
    private String name;
    private int age;
    //引用类型
    private Subject subject;

    public Student(String name, int age, Subject subject) {
        this.name = name;
        this.age = age;
        this.subject = subject;
    }

    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 Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

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

    @Override
    public Object clone() throws CloneNotSupportedException {
        //直接拷贝
        return super.clone();
    }
}
import domain.Student;
import domain.Subject;

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);//原对象
        Student student_copy = (Student) student.clone();//拷贝对象
        //修改原对象
        student.setName("jqp");
        student.setAge(26);
        subject.setName("english");
        subject.setScore(95);
        student.setSubject(subject);
        System.out.println(student);
        System.out.println(student_copy);
    }
}

输出结果:

Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}
Student{name=‘pjq’, age=25, subject=Subject{name=‘english’, score=95}}

浅拷贝图
对于引用类型,如果是浅拷贝,那么更改其中一个会影响多个。基本类型是值传递的,更改一个不会影响拷贝的。而字符串类型是存在常量池StringTable中的,更改相当于在StringTable中新创建一个字符串,其实就是深拷贝。

深拷贝

自己编写的类,如何实现深拷贝呢?一种方法是重写clone方法。

package domain;

public class Subject implements Cloneable{
    private String name;
    private int score;

    public Subject(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

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

    @Override
    public Object clone() throws CloneNotSupportedException {
        //Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
        return super.clone();
    }
}
package domain;

public class Student implements Cloneable {
    //基础类型和String不可变对象
    private String name;
    private int age;
    //引用类型
    private Subject subject;

    public Student(String name, int age, Subject subject) {
        this.name = name;
        this.age = age;
        this.subject = subject;
    }

    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 Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

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

    @Override
    public Object clone() throws CloneNotSupportedException {
        //深拷贝
        Student student = (Student) super.clone();
        student.subject = (Subject) subject.clone();
        return student;
    }
}
import domain.Student;
import domain.Subject;

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);//原对象
        Student student_copy = (Student) student.clone();//拷贝对象
        //修改原对象
        student.setName("jqp");
        student.setAge(26);
        subject.setName("english");
        subject.setScore(95);
        student.setSubject(subject);
        System.out.println(student);
        System.out.println(student_copy);
    }
}

输出结果:

Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}
Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}

通过结果可以看出此时是深拷贝

深拷贝

无法重写clone()时

上文给出了一种深拷贝的方法,即通过重写clone的方式,但是如果不是我们自己写的类,例如,想要深拷贝一个容器内的所有内容,怎么实现?以下先展示几种错误方式。

遍历复制

import domain.Student;
import domain.Subject;

import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);
        List<Student> students = new ArrayList<>();//原始List
        students.add(student);
        List<Student> students_copy = new ArrayList<>();//拷贝的List
        for (int i = 0; i < students.size(); ++i) {
            students_copy.add(students.get(i));
        }

        //修改原始List
        students.get(0).setName("jqp");
        students.get(0).setAge(26);
        students.get(0).getSubject().setName("english");
        students.get(0).getSubject().setScore(95);

        System.out.println(students);
        System.out.println(students_copy);
    }
}

输出结果:

[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]

从结果看出,毫无疑问是浅拷贝。

使用构造方法传参复制

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);
        List<Student> students = new ArrayList<>();//原始List
        students.add(student);
        List<Student> students_copy = new ArrayList<>(students);//拷贝的List
        
        //修改原始List
        students.get(0).setName("jqp");
        students.get(0).setAge(26);
        students.get(0).getSubject().setName("english");
        students.get(0).getSubject().setScore(95);

        System.out.println(students);
        System.out.println(students_copy);
    }
}

输出结果:

[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]

同样是浅拷贝

使用list.addAll复制

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);
        List<Student> students = new ArrayList<>();//原始List
        students.add(student);
        List<Student> students_copy = new ArrayList<>();//拷贝的List
        students_copy.addAll(students);

        //修改原始List
        students.get(0).setName("jqp");
        students.get(0).setAge(26);
        students.get(0).getSubject().setName("english");
        students.get(0).getSubject().setScore(95);

        System.out.println(students);
        System.out.println(students_copy);
    }
}

输出结果:

[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]

还是浅拷贝。

其实查看源码就知道,list.addAll()是使用的System.arraycopy,而System.arraycopy是一个浅拷贝,那么Array.copyOf显然也是浅拷贝,它底层也是调用的System.arraycopy。

那么到底如何深拷贝呢?

遍历new

显然,深拷贝要为容器中的每个元素重新分配内存。

import domain.Student;
import domain.Subject;

import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);
        List<Student> students = new ArrayList<>();//原始List
        students.add(student);
        List<Student> students_copy = new ArrayList<>();//拷贝的List
        for (int i = 0; i < students.size(); ++i) {
            //不仅student对象要分配内存,它student对象内部的subject对象也要重新分配,否则还是subject对象还是浅拷贝
            Subject sbuject_temp = new Subject(students.get(i).getSubject().getName(), students.get(i).getSubject().getScore());
            Student student_temp = new Student(students.get(i).getName(), students.get(i).getAge(), sbuject_temp);
            students_copy.add(student_temp);
        }

        //修改原始List
        students.get(0).setName("jqp");
        students.get(0).setAge(26);
        students.get(0).getSubject().setName("english");
        students.get(0).getSubject().setScore(95);

        System.out.println(students);
        System.out.println(students_copy);
    }
}

输出结果:

[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}]

实现了深拷贝,但是还存在一个明显的问题:不仅要为List内部的每一个Student重新分配内存,每一个Student内部的每一个Subject还要重新分配内存,如果Subject内部还有其他引用类型,显然也要重新分配内存,太麻烦了。

序列化深拷贝

import domain.Student;
import domain.Subject;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

class Main {
    public static List<Student> deepCopy(List<Student> students) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(students);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return (List<Student>) objectInputStream.readObject();
    }

    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Subject subject = new Subject("math", 90);
        Student student = new Student("pjq", 25, subject);
        List<Student> students = new ArrayList<>();//原始List
        students.add(student);
        List<Student> students_copy = deepCopy(students);//拷贝的List
        
        //修改原始List
        students.get(0).setName("jqp");
        students.get(0).setAge(26);
        students.get(0).getSubject().setName("english");
        students.get(0).getSubject().setScore(95);

        System.out.println(students);
        System.out.println(students_copy);
    }
}

输出结果:

[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}]

注意:这种方法需要Student类和Subject类实现Serializable接口。

参考

Java 浅拷贝和深拷贝

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值