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

含义

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

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

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

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

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

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

// 上面一行代码和这两行代码一个效果,copyProperties也是浅复制,针对对象里面的变量进行内存地址复制
Person person2 = new Person();
BeanUtils.copyProperties(person1, person2);

在这个例子中,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、复制、引用

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


    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);
    }

打印:

// 因为 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=北京)

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();
            }
        }

打印

原始值    --->  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=重庆)

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;
        }
    }
}

打印:

原始值    --->  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=四川)

例子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;
    }


}

打印:

原始值    --->  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=四川)

总结

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值