Java对象拷贝的浅与深:如何选择?

在日常开发中,我们经常需要将一个对象的属性复制到另一个对象中。无论是使用第三方工具类还是自己手动实现,都会涉及到浅拷贝深拷贝的问题。本文将深入讨论浅拷贝的潜在风险,并给出几种实现深拷贝的方式,帮助大家避免潜在的坑。

一、什么是浅拷贝?

在Java中,浅拷贝只会复制对象的基本类型字段,而对引用类型字段只复制引用的内存地址,不会递归复制引用的对象。这意味着,多个对象共享同一个引用,修改其中一个对象的引用字段可能会影响其他对象。

示例:Hutool和Apache Common工具类的浅拷贝

在项目中我们常使用工具类如 HutoolBeanUtil.copyProperties() 或 Apache Commons 的 BeanUtils.copyProperties() 来进行对象的拷贝。这些工具类默认情况下都执行浅拷贝。

本篇以Hutool的举例,依赖如下

      <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
import cn.hutool.core.bean.BeanUtil;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User {
    private Long userId;
    private String name;
    private String email;

    public static void main(String[] args) {
        User oldUser = new User(1L, "lps", "email");
        User newUser = new User();

        // 使用 Hutool 工具类拷贝属性
        BeanUtil.copyProperties(oldUser, newUser);

        // 修改原对象的 userId
        oldUser.setUserId(2L);

        // 输出新对象的属性
        System.out.println(newUser); // 结果:User(userId=1, name=lps, email=email)
    }
}

这个例子中的 Hutool 工具类对 oldUser 进行了浅拷贝。修改 oldUseruserId 并不会影响 newUser,因为 Long 是不可变类型。但如果 User 类中包含引用类型(例如 List、自定义对象),浅拷贝就会带来问题。

二、浅拷贝的潜在问题

浅拷贝最大的风险在于引用类型数据的共享。当你修改一个对象中的引用字段时,拷贝出来的对象也会随之改变。

示例:浅拷贝带来的问题
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class Address {
    private String city;
}

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User {
    private Long userId;
    private String name;
    private String email;
    private Address address;

    public static void main(String[] args) {
        Address address = new Address("Beijing");
        User oldUser = new User(1L, "lps", "email", address);
        User newUser = new User();

        // 浅拷贝 oldUser 到 newUser
        BeanUtil.copyProperties(oldUser, newUser);

        // 修改 oldUser 的地址
        oldUser.getAddress().setCity("Shanghai");

        // 输出新对象的地址
        System.out.println(newUser.getAddress().getCity()); // 结果:"Shanghai"
    }
}

在这个例子中,修改了 oldUseraddress 对象,导致 newUseraddress 也被改变。这就是浅拷贝的典型问题。

三、深拷贝:如何避免共享引用的问题?

为了避免浅拷贝带来的问题,深拷贝通过递归地复制所有引用对象来确保两个对象完全独立。实现深拷贝有多种方式,下面介绍几种常见的做法。

1. 手动实现深拷贝

最常见的方法是手动在 clone() 方法中递归调用所有引用对象的 clone() 方法。

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class Address implements Cloneable {
    private String city;

    @Override
    protected Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); 
        }
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User implements Cloneable {
    private Long userId;
    private String name;
    private String email;
    private Address address;

    @Override
    protected User clone() {
        try {
            User cloned = (User) super.clone();
            cloned.setAddress(this.address.clone());  // 手动深拷贝
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

手动实现深拷贝虽然可以控制每个引用的拷贝逻辑,但对于复杂对象来说,编写和维护都比较繁琐。


2. 使用序列化实现深拷贝

序列化是另一种常见的深拷贝方法,它通过将对象序列化为字节流,再反序列化为新的对象来实现深拷贝。

public User deepCopy() {
    try {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(this);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        return (User) in.readObject();
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException("深拷贝失败", e);
    }
}

虽然序列化方法较为简单通用,但它要求所有参与拷贝的类都实现 Serializable 接口,并且序列化和反序列化的性能开销较大。

四、总结
  • 浅拷贝:通过工具类如 HutoolApache Commons 可以轻松实现属性拷贝,但要小心引用类型字段的共享问题。
  • 深拷贝:如果需要完整独立的对象,深拷贝是必要的。你可以选择手动实现 clone() 或使用序列化方式实现。

在选择合适的拷贝方式时,应根据对象的复杂度和性能需求作出决策。如果对象层级简单且性能要求较高,手动实现 clone() 是不错的选择;如果对象层级较复杂,可以考虑使用序列化来简化深拷贝的实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值