• Java拷贝之深拷贝与浅拷贝


    Java中的拷贝分为引用拷贝和对象拷贝

    1、引用拷贝(浅拷贝

    引用拷贝:只会生成一个新的对象引用地址,但两个对峙最终指向的还是同一个对象
    代码示例:

    // 定义的测试对象
    @Data
    public class User {
        private String name;
        private String age;
        private String address;
        public User(String name, String age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    }
    
    // 测试类
    @Test
    void testCopy() {
        User userA = new User("john", "23", "西斗门路");
        System.out.println(userA);
        User userB = userA;
        userB.setName("matt");
        System.out.println("userA:" + userA);
        System.out.println("userB:" + userB);
    }
    
    /** 执行结果
     * User(name=john, age=23, address=西斗门路)
     * userA:User(name=matt, age=23, address=西斗门路)
     * userB:User(name=matt, age=23, address=西斗门路)
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看到,在修改userB的属性后,userA的属性也一同被修改了,说明此时二者的引用对象都是同一个。

    2、对象拷贝(深拷贝/浅拷贝)

    对象拷贝:会重新生成一个对象,新生成的对象和之前的对象没有任何关联。
    对象拷贝中,区分为两类:对象浅拷贝、对象深拷贝

    2.1、对象浅拷贝

    在克隆操作中国,只复制对象本身以及对象内部的基本数据类的属性,而不会复制对象内部的引用类型的属性。
    浅拷贝仅仅创建了一个新的对象,该对象与原对象的引用类型属性还是指向同一地址,当应用类型发生修改时,二者都会变更。
    在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。
    想要实现对象浅拷贝,可以通过实现Cloneable接口重写其clone方法
    代码示例:

    // 定义的测试对象
    @Data
    public class User implements Cloneable{
        private String name;
        private String age;
        private String address;
        private Father father;
        public User(String name, String age, String address, Father father) {
            this.name = name;
            this.age = age;
            this.address = address;
            this.father = father;
        }
        public User copyUser() throws CloneNotSupportedException {
            return (User) clone();
        }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    @Data
    public class Father {
        private String name;
        public Father(String name) {
            this.name = name;
        }
    }
    
    @Test
    void testCopy() {
        Father fatherA = new Father("William");
        User userA = new User("john", "23", "西斗门路", fatherA);
        System.out.println(userA);
        User userB = null;
        try {
            userB = userA.copyUser();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        userB.setName("matt");
        userB.getFather().setName("Billion");
        System.out.println("userA:" + userA);
        System.out.println("userB:" + userB);
    }
    
    /**
     * 执行结果
     * User(name=john, age=23, address=西斗门路, father=Father(name=William))
     * userA:User(name=john, age=23, address=西斗门路, father=Father(name=Billion))
     * userB:User(name=matt, age=23, address=西斗门路, father=Father(name=Billion))
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    2.2、对象深拷贝

    • 深拷贝指的是在复制对象的过程中,除了复制对象及其基本类型的属性时,还要递归的复制对象内部的引用类型的属性。
    • 深拷贝会创建一个完全独立的对象, 新对象和原对象不会产生任何关联,各自的属性值修改互不影响

    实现有两个方案:
    1、实现Serializable接口,通过序列化方式进行克隆(下面示例使用方式)
    2、重写Cloneable的clone方法,即对原对象的引用类型的属性二次调用clone方法进行克隆
    代码示例:

    // 创建对象,注意都需要实现Serializable接口
    @Data
    public class User implements Serializable {
        private String name;
        private String age;
        private String address;
        private Father father;
        public User(String name, String age, String address, Father father) {
            this.name = name;
            this.age = age;
            this.address = address;
            this.father = father;
        }
    }
    
    @Data
    public class Father implements Serializable {
        private String name;
        public Father(String name) {
            this.name = name;
        }
    }
    
    // 通过序列化实现深拷贝的方法
    public static <T extends Serializable> T deepCopy(T object) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
            // 序列化对象
            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                 ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
                // 反序列化对象,实现深拷贝
                return (T) objectInputStream.readObject();
            }
        } catch (IOException | ClassNotFoundException e) {
            // 处理n异常
            e.printStackTrace();
        }
        // 返回null,表示深拷贝失败
        return null;
    }
    
    // 测试
    @Test
    void testCopy() {
        Father fatherA = new Father("William");
        User userA = new User("john", "23", "西斗门路", fatherA);
        System.out.println(userA);
        User userB = deepCopy(userA);
        userB.setName("matt");
        userB.getFather().setName("Billion");
        System.out.println("userA:" + userA);
        System.out.println("userB:" + userB);
    }
    
    /**
     * 执行结果
     * User(name=john, age=23, address=西斗门路, father=Father(name=William))
     * userA:User(name=john, age=23, address=西斗门路, father=Father(name=William))
     * userB:User(name=matt, age=23, address=西斗门路, father=Father(name=Billion))
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    附带知识:使用try-with-resources:在Java 7及更高版本中,可以使用try-with-resources语句来自动关闭实现了Closeable接口的资源。在这段代码中,ByteArrayOutputStream、ObjectOutputStream、ByteArrayInputStream和ObjectInputStream都实现了Closeable接口,因此可以使用try-with-resources来自动关闭这些资源,而无需手动调用close方法。

  • 相关阅读:
    安卓开发基础知识-补习8
    【学习笔记15】JavaScript的函数
    分布式:Docker
    一个可见又不可见的窗口
    Abnova ACTN4纯化兔多克隆抗体说明书
    提交有文件和其它文本内容的表单
    八股文之jdk源码分析
    IT廉连看——Uniapp——配置文件pages
    【RocketMQ】RocketMQ存储结构设计
    Nuxt - 超详细 @nuxtjs/axios 介绍封装请求及拦截器配置(脚手架自带请求库)
  • 原文地址:https://blog.csdn.net/weixin_46508271/article/details/132787115