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方法。

相关推荐
苹果醋316 分钟前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
晓纪同学32 分钟前
QT-简单视觉框架代码
开发语言·qt
威桑32 分钟前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
Hello.Reader35 分钟前
深入解析 Apache APISIX
java·apache
飞飞-躺着更舒服35 分钟前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生41 分钟前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生44 分钟前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb
Java Fans1 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手1 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
菠萝蚊鸭1 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel