• 深入理解Java中的引用、复制、克隆和拷贝


    含义

    引用在Java中,所有对象变量都是引用,它们存储的是对象的地址(在堆内存中),而不是对象本身。当你创建一个对象并将其赋值给一个变量时,这个变量实际上存储的是对象的引用,这个引用指向对象在内存中的位置。

    Person person1 = new Person("Alice"); // 可以理解为创建一个Person对象,然后返回的对象引用被赋给了 person1 变量
    
    • 1

    在上面的代码中,person1 是一个引用,它指向了一个 Person 对象在内存中的地址。

    • 复制(赋值)当你将一个对象变量赋值给另一个变量时,实际上是在复制对象的引用,而不是对象本身。这意味着两个变量指向同一个对象,它们共享同一块内存空间,因此复制也可以理解为引用。
    Person person1 = new Person("Alice");
    Person person2 = person1; // 复制(引用)了 person1,其实person1也是引用了Person对象
    
    • 1
    • 2

    在这个例子中,person2 和 person1 都指向同一个 Person 对象。

    • 克隆(拷贝):克隆可以理解为拷贝,分为浅拷贝(浅复制)、深拷贝(深复制)对象的克隆意味着创建一个新的对象,它的值和原始对象相同,但它在内存中占据不同的位置。Java 中的 clone 方法可以用于实现对象的浅拷贝,需要注意的是默认的 clone 方法执行的是浅拷贝,即它只复制对象的字段值,而不复制对象引用的内容。
    Person person2 = (Person) person1.clone(); // 与 Person person2 = person1; 不同的是,克隆是引用对象变量的内存地址;复制是直接引用对象内存地址
    
    // 上面一行代码和这两行代码一个效果,copyProperties也是浅复制,针对对象里面的变量进行内存地址复制
    Person person2 = new Person();
    BeanUtils.copyProperties(person1, person2);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这个例子中,person2 和 person1 是两个不同的对象,但它们的值相同,但在内存中存储在不同的位置。

    综上所述,引用是指对象变量存储的是对象的地址;复制是将对象的引用赋值给另一个变量;克隆是创建一个新的对象,其值和原始对象相同;拷贝是将对象的属性值复制到另一个对象中。需要注意的是克隆和拷贝的方式可以是浅复制,也可以是深复制,具体取决于实现方式。

    举例

    基础实体类

    @Data
    @AllArgsConstructor
    class Address {
        private String city;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Person implements Cloneable {
        private String name;
        private Address address;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1、复制、引用

    以下就是一个简单的对象复制,或者说是引用的例子

    
        public static void main(String[] args) {
            Address address = new Address("重庆");
            Person person1 = new Person("小明", address);
    
            Person person2 = person1; // 引用 person1 对象的地址(在堆内存中),person2 和 person1 都指向同一个 Person 对象。
            System.out.println("原始值    --->  " + person2);
            System.out.println("person1.equals(person2)   --->  " + person1.equals(person2));
            System.out.println("person1 == person2   --->  " + (person1 == person2));
    
            // 修改 person2 的字段
            person2.setName("小白"); // 修改了引用对象的值
            person2.getAddress().setCity("北京"); // address.setCity("北京"); 一个意思,修改的是源地址 address 对象中的变量
    
            System.out.println("person1  --->  " + person1);
            System.out.println("person2  --->  " + person2);
            System.out.println("address  --->  " + address);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印:

    // 因为 person2 和 person1 都指向同一个 Person 对象,所以无论修改person2 还是 person1,都会改变
    原始值    --->  Person(name=小明, address=Address(city=重庆))
    person1.equals(person2)   --->  true
    person1 == person2   --->  true
    person1  --->  Person(name=小白, address=Address(city=北京))
    person2  --->  Person(name=小白, address=Address(city=北京))
    address  --->  Address(city=北京)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、浅拷贝

    public static void main(String[] args) {
                Address address = new Address("四川");
                Person person1 = new Person("小明", address);
    
                try {
                    // 浅拷贝,先声明一个person类的person2对象,然后把person1里面的变量(name,address)地址引用复制到了person2中,
                    Person person2 = (Person) person1.clone();
                    System.out.println("原始值    --->  " + person2);
                    System.out.println("person1.equals(person2)   --->  " + person1.equals(person2));
                    System.out.println("person1 == person2   --->  " + (person1 == person2));
                    
                    person2.setName("小白"); // String是定长,字符串小白会分配另一个内存地址,所以其实是重新把name变量指向了另一个地址
                    person2.getAddress().setCity("重庆");
    
                    System.out.println("person1  --->  " + person1);
                    System.out.println("person2  --->  " + person2);
                    System.out.println("address  --->  " + address);
                   
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    打印

    原始值    --->  Person(name=小明, address=Address(city=四川))
    person1.equals(person2)   --->  true
    person1 == person2   --->  false
    // 跟前面对象引用不一样的就是,浅拷贝是复制的对象里面变量的内存地址,
    // 而name是个定长字符串,修改为小白后,person2中name的引用地址就变了,而person1中name还是指向原来的小明
    // 而address因为是个对象,person2.getAddress().setCity("重庆")修改的是address中city这个字段的引用,而person1和person2是同一个address对象,所以同时改变
    person1  --->  Person(name=小明, address=Address(city=重庆))
    person2  --->  Person(name=小白, address=Address(city=重庆))
    address  --->  Address(city=重庆)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3、深拷贝

    例子1(序列化):

    package org.swp.controller;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.*;
    
    public class Test {
    
            public static void main(String[] args) {
                Address address = new Address("四川");
                Person person1 = new Person("小明", address);
    
                // 缓存原始对象
                Person person2 = person1.deepCopy();
                System.out.println("原始值    --->  " + person2);
                System.out.println("person1.equals(person2)   --->  " + person1.equals(person2));
                System.out.println("person1 == person2   --->  " + (person1 == person2));
                
                // 修改缓存中的对象
                person2.setName("小白");
                person2.getAddress().setCity("重庆");
    
                System.out.println("person1  --->  " + person1);
                System.out.println("person2  --->  " + person2);
                System.out.println("address  --->  " + address);
            }
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Address implements Serializable {
        private String city;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Person implements Serializable {
        private String name;
        private Address address;
    
        public Person deepCopy() {
            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream out = new ObjectOutputStream(bos);
                out.writeObject(this);
    
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream in = new ObjectInputStream(bis);
                return (Person) in.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    • 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

    打印:

    原始值    --->  Person(name=小明, address=Address(city=四川))
    person1.equals(person2)   --->  true
    person1 == person2   --->  false
    person1  --->  Person(name=小明, address=Address(city=四川))
    person2  --->  Person(name=小白, address=Address(city=重庆))
    address  --->  Address(city=四川)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    例子2(close):

    package org.swp.controller;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    public class Test {
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Address address = new Address("四川");
            Person person1 = new Person("小明", address);
    
            // 缓存原始对象
            Person person2 = (Person) person1.clone();
            System.out.println("原始值    --->  " + person2);
            System.out.println("person1.equals(person2)   --->  " + person1.equals(person2));
            System.out.println("person1 == person2   --->  " + (person1 == person2));
    
            // 修改缓存中的对象
            person2.setName("小白");
            person2.getAddress().setCity("重庆");
    
            System.out.println("person1  --->  " + person1);
            System.out.println("person2  --->  " + person2);
            System.out.println("address  --->  " + address);
        }
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Address implements Serializable {
        private String city;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Person implements Cloneable {
        private String name;
        private Address address;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            Person clonedPerson = (Person) super.clone();
            clonedPerson.address =new Address(this.address.getCity());
            return clonedPerson;
        }
    
    
    }
    
    • 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

    打印:

    原始值    --->  Person(name=小明, address=Address(city=四川))
    person1.equals(person2)   --->  true
    person1 == person2   --->  false
    person1  --->  Person(name=小明, address=Address(city=四川))
    person2  --->  Person(name=小白, address=Address(city=重庆))
    address  --->  Address(city=四川)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    引用:内存地址的指向
    复制:java中复制就是对象地址的引用
    拷贝(克隆):分为浅拷贝、深拷贝
    浅复制(浅拷贝):在复制对象时,只会复制对象本身,而不会复制对象内部的引用类型成员变量,这样会导致新对象和原对象共享引用类型成员变量
    深复制(深拷贝):在复制对象时,会递归地复制对象本身以及其内部的引用类型成员变量,以确保新对象和原对象完全独立。

  • 相关阅读:
    SAP UI5 指定 / 变更版本
    【语义分割 01】Open MMLab介绍
    Haskell用户定义的类型
    供暖系统如何实现数据远程采集?贝锐蒲公英高效实现智慧运维
    多线程详解
    centos7 探测某个tcp端口是否在监听
    企业选型OA系统 ,如何选择合适的?
    【正点原子STM32连载】第八章 新建HAL版本MDK工程 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
    宝塔配置tomcat
    学生作业-购物网站-视频网站-仿百度网站-迪士尼网站-游戏网站-个人主页-个人介绍-音乐网站(作业源码)
  • 原文地址:https://blog.csdn.net/weixin_44183847/article/details/134404629