Java拷贝之深拷贝与浅拷贝

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

1、引用拷贝(浅拷贝)

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

代码示例:

java 复制代码
// 定义的测试对象
@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=西斗门路)
 */

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

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

对象拷贝:会重新生成一个对象,新生成的对象和之前的对象没有任何关联。

对象拷贝中,区分为两类:对象浅拷贝、对象深拷贝

2.1、对象浅拷贝

在克隆操作中国,只复制对象本身以及对象内部的基本数据类的属性,而不会复制对象内部的引用类型的属性。

浅拷贝仅仅创建了一个新的对象,该对象与原对象的引用类型属性还是指向同一地址,当应用类型发生修改时,二者都会变更。

在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。

想要实现对象浅拷贝,可以通过实现Cloneable接口重写其clone方法

代码示例:

java 复制代码
// 定义的测试对象
@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))
 */

2.2、对象深拷贝

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

实现有两个方案:

1、实现Serializable接口,通过序列化方式进行克隆(下面示例使用方式)

2、重写Cloneable的clone方法,即对原对象的引用类型的属性二次调用clone方法进行克隆

代码示例:

java 复制代码
// 创建对象,注意都需要实现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))
 */

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

相关推荐
kruptos3 小时前
分布式系统怎么“选主“?Raft 共识一次讲清
开发语言·分布式·php·共识算法
xcl09253 小时前
幼儿托育系统开发实战:从需求分析到上线全流程指南
java·大数据·需求分析
吴声子夜歌3 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
小小猪的春天3 小时前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
find1star3 小时前
LeetCode 141:环形链表
java·算法·leetcode·链表
今天AI了吗4 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee
benchmark_cc4 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
王的宝库4 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
Tairitsu_H4 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
许彰午4 小时前
27-AuthService登录链路
java·低代码·架构