Java 的引用拷贝、浅拷贝、深拷贝

半城伤御伤魂 提交于 2020-08-11 17:23:18

1、引用拷贝

将对象的引用赋值给新的对象,也就是两个变量其实指向的是同一个对象实例。

public class Main {
    static class Teacher {
        private String name;
				public Teacher(String name) {
            this.name = name;
        }
        // 省略 getter setter
    }

    public static void main(String[] args) {
        Teacher teacher1 = new Teacher("张三");
        // 这里只是把 teacher1 对象的引用赋值给 teacher2
        Teacher teacher2 = teacher1;
        // 不管怎么运行,以下输出的两个对象地址是相同的
        System.out.println(teacher1);
        System.out.println(teacher2);
      
        // 修改 teacher1 同样也会影响 teacher2
      	teacher1.setName("李四");
      	// 输出结果为 李四
      	System.out.println(teacher2.getName());
    }
}

teacher1 和 teacher2 输出的对象地址是相同的,说明其指向的对象是相同的。teacher1 只是把引用地址赋值给了 teacher2,这就是引用拷贝。

image-20200804110811119

2、浅拷贝

浅拷贝需要实现 Cloneable 接口,浅拷贝会把当前对象复制一份,但不会拷贝其成员变量的引用指向的对象,也就是说:浅拷贝只拷贝当前的对象,而不关心其他的对象

2.1、示例 1:

public class Main {
    static class Teacher implements Cloneable  {
        private String name;
        // 省略构造函数,getter setter
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher1 = new Teacher("张三");
        Teacher teacher2 = (Teacher) teacher1.clone();
        // 会看到不同的地址
        System.out.println(teacher1);
        System.out.println(teacher2);
        // 修改 teacher1 的值,会看到不同的地址
        teacher1.setName("李四");
        System.out.println(teacher1.getName());
        System.out.println(teacher2.getName());
    }
}

可以看到两天不同的对象地址,修改 teacher1.setName("李四") 的值不会影响 teacher2。

image-20200804113638514

2.2、示例2,如果类中还有对象的引用

像下面示例中 Student 中有 Teacher 对象的引用。

public class Main {
    static class Teacher implements Cloneable  {
        private String name;
        // 省略构造函数,getter,setter
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    static class Student implements Cloneable{
        private String name;
        private Teacher teacher;
        // 省略构造函数,getter setter
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
				Teacher teacher = new Teacher("张三");
        Student student1 = new Student("李四",teacher);
        Student student2 = (Student) student1.clone();
        // 修改 student1 的 teacher
        student1.getTeacher().setName("王五");
        // 获取 student2 的teacher 名字,会输出 王五
        System.out.println(student2.getTeacher().getName());
        // 比较两者的地址,会输出 true,说明 teacher 对象没有拷贝
      	System.out.println(student1.getTeacher() == student2.getTeacher())
    }
}

最后的结果输出是 “王五”,不是已经拷贝 student1 指向的对象实例了吗?浅拷贝的一个特点就是只拷贝当前的对象,也就是 Student ,并不会继续拷贝当前对象以外的其他对象,其他对象依旧是使用引用拷贝。例如 private Teacher teacher;

image-20200804114812285

3、深拷贝

相对于浅拷贝,深拷贝不只拷贝当前的对象,也会把成员变量中引用指向的对象也拷贝一份,如下图所示:

  • 深拷贝会把当前对象所引用的所有的对象都会拷贝一份。

image-20200804123041447

3.1、深拷贝实现方式 1,实现 Cloneable

这种方式很麻烦,如果 Teacher 里面还有个 private School school; 的对象引用,则还需要继续修改 Teacher 类和 School 类实现 Cloneable 接口。其明显的缺点是工作量大,代码繁多。

package org.xian;


import java.util.Objects;

public class Main {
    static class Teacher implements Cloneable  {
        private String name;
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    }

    static class Student implements Cloneable{
        private String name;
        private Teacher teacher;
        @Override
        protected Student clone() throws CloneNotSupportedException {
            // 先浅拷贝当前的对象
            Student student = (Student) super.clone();
            // 将当前的 Teacher 对象再拷贝一份
            Teacher teacher = (Teacher) this.getTeacher().clone();
            // 设置 teacher
            student.setTeacher(teacher);
            return student;
        }

    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("张三");
        Student student1 = new Student("李四",teacher);
        Student student2 = (Student) student1.clone();
        // 修改 student1 的 teacher
        student1.getTeacher().setName("王五");
        // 获取 student2 的teacher 名字,会输出 张三,说明 teacher 也拷贝了一份
        System.out.println(student2.getTeacher().getName());
        // 比较两者的地址,会输出 false,说明 teacher 重新拷贝一份
        System.out.println(student1.getTeacher() == student2.getTeacher());
    }
}

3.2、使用序列化的方式

需要实现系列化的接口,需要所有使用的类实现 Serializable 接口。例如 Teacher 新增一个 School school; 那么 School 也需要实现 Serializable 接口。

public class Main {
    static class Teacher implements  Serializable {
        private String name;
    }

    static class Student implements Serializable{
        private String name;
        private Teacher teacher;
        public Student deepClone() throws IOException, ClassNotFoundException {
            // 序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);

            oos.writeObject(this);

            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Student) ois.readObject();
        }

    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Teacher teacher = new Teacher("张三");
        Student student1 = new Student("李四",teacher);
        Student student2 = student1.deepClone();
        // 修改 student1 的 teacher
        student1.getTeacher().setName("王五");
        // 获取 student2 的teacher 名字,会输出 张三,说明 teacher 也拷贝了一份
        System.out.println(student2.getTeacher().getName());
        // 比较两者的地址,会输出 false,说明 teacher 重新拷贝一份
        System.out.println(student1.getTeacher() == student2.getTeacher());
    }
}

参考:https://blog.csdn.net/baiye_xing/article/details/71788741

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!