浅拷贝与深拷贝:对象复制的隐藏风险

前言

请添加图片描述

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家https://www.captainbed.cn/z
在这里插入图片描述
请添加图片描述


1. 错误场景复现

场景1:浅拷贝导致数据篡改

class User implements Cloneable {
    
    
    private String name;
    private List<String> permissions; // 引用类型字段
    
    @Override
    public User clone() {
    
    
        try {
    
    
            return (User) super.clone(); // 默认浅拷贝
        } catch (CloneNotSupportedException e) {
    
    
            throw new AssertionError();
        }
    }
}

User user1 = new User("Alice", Arrays.asList("read", "write"));
User user2 = user1.clone();

user2.getPermissions().add("delete"); // 修改拷贝对象的权限
System.out.println(user1.getPermissions()); // 输出:[read, write, delete](原对象被污染!)

后果:通过浅拷贝复制的对象与原对象共享内部引用,导致数据意外篡改。


场景2:Cloneable接口的陷阱

class Order implements Cloneable {
    
    
    private Long id;
    private Date createTime; // 可变引用类型
    
    @Override
    public Order clone() {
    
    
        return (Order) super.clone(); // 浅拷贝
    }
}

Order order1 = new Order(1L, new Date());
Order order2 = order1.clone();

order2.getCreateTime().setTime(0L); // 修改拷贝对象的时间
System.out.println(order1.getCreateTime()); // 输出:Thu Jan 01 08:00:00 CST 1970(原对象被修改)

隐患:即使简单对象,若包含可变引用字段,浅拷贝仍可能引发问题。


场景3:Spring原型Bean的共享问题

@Scope("prototype")
@Component
class PrototypeBean {
    
    
    private Map<String, Object> context = new HashMap<>(); // 可变状态
}

@Autowired
private ObjectFactory<PrototypeBean> prototypeFactory;

public void process() {
    
    
    PrototypeBean bean1 = prototypeFactory.getObject();
    bean1.getContext().put("key", "value1");
    
    PrototypeBean bean2 = prototypeFactory.getObject();
    bean2.getContext().put("key", "value2"); 

    // 期望bean1和bean2独立,但若Spring通过浅拷贝实现原型,可能导致context共享
}

后果:若框架通过克隆实现原型模式,浅拷贝会导致状态意外共享。


2. 原理解析

浅拷贝 vs 深拷贝

特性 浅拷贝(Shallow Copy) 深拷贝(Deep Copy)
定义 复制对象本身,不复制内部引用指向的对象 递归复制对象及其所有嵌套引用对象
内存占用 低(共享引用) 高(完全独立副本)
实现复杂度 简单(默认clone() 复杂(需手动处理所有引用字段)
线程安全 可能不安全(共享可变状态) 安全(完全隔离)

Object.clone()的缺陷

  1. 脆弱性:需实现Cloneable标记接口,但clone()方法定义在Object
  2. 浅拷贝默认行为:对数组是深拷贝(特殊处理),其他引用类型均为浅拷贝
  3. 不可控性:无法通过接口约束clone方法的实现

3. 正确解决方案

方案1:深拷贝实现四板斧

class Order implements Cloneable {
    
    
    private Long id;
    private Date createTime;
    private List<Item> items;

    // 1. 复制构造函数(推荐)
    public Order(Order other) {
    
    
        this.id = other.id;
        this.createTime = new Date(other.createTime.getTime()); // 深拷贝Date
        this.items = other.items.stream()
                               .map(Item::new) // Item也需实现深拷贝
                               .collect(Collectors.toList());
    }

    // 2. 重写clone实现深拷贝
    @Override
    public Order clone() {
    
    
        try {
    
    
            Order cloned = (Order) super.clone();
            cloned.createTime = (Date) createTime.clone();
            cloned.items = items.stream()
                              .map(Item::clone)
                              .collect(Collectors.toList());
            return cloned;
        } catch (CloneNotSupportedException e) {
    
    
            throw new AssertionError();
        }
    }

    // 3. 工具类辅助(Apache Commons Lang)
    public Order deepCopyBySerialization() {
    
    
        return SerializationUtils.clone(this); // 需实现Serializable
    }

    // 4. JSON序列化(Jackson)
    public Order deepCopyByJson(ObjectMapper mapper) throws JsonProcessingException {
    
    
        String json = mapper.writeValueAsString(this);
        return mapper.readValue(json, Order.class);
    }
}
选型建议
  • 简单对象:复制构造函数
  • 复杂对象图:序列化方案(确保所有嵌套对象可序列化)
  • 高性能场景:手动逐层深拷贝

方案2:不可变对象设计

// 所有字段final,返回新对象而非修改
public final class ImmutableUser {
    
    
    private final String name;
    private final List<String> permissions; // 使用不可变集合
    
    public ImmutableUser(String name, List<String> permissions) {
    
    
        this.name = name;
        this.permissions = List.copyOf(permissions); // 防御性拷贝
    }
    
    public ImmutableUser withPermission(String permission) {
    
    
        List<String> newPermissions = new ArrayList<>(this.permissions);
        newPermissions.add(permission);
        return new ImmutableUser(this.name, newPermissions);
    }
}
优势
  • 天然线程安全
  • 无需担心拷贝问题(每次修改生成新对象)

方案3:防御性拷贝

class SecurityConfig {
    
    
    private final List<String> allowedIPs;
    
    public SecurityConfig(List<String> allowedIPs) {
    
    
        // 构造时拷贝
        this.allowedIPs = new ArrayList<>(allowedIPs);
    }
    
    public List<String> getAllowedIPs() {
    
    
        // 返回不可修改视图
        return Collections.unmodifiableList(allowedIPs);
    }
}
关键点
  • 在构造器和getter中保护内部状态
  • 使用Collections.unmodifiableXXX防止外部修改

4. 工具与最佳实践

深拷贝工具库

  1. Apache Commons Lang
    Order copy = SerializationUtils.clone(original); // 需实现Serializable
    
  2. JSON序列化(Jackson/Gson)
    ObjectMapper mapper = new ObjectMapper();
    Order copy = mapper.readValue(mapper.writeValueAsString(original), Order.class);
    
  3. MapStruct
    @Mapper
    public interface OrderMapper {
          
          
        OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
        Order deepCopy(Order order);
    }
    

静态分析工具

  • SonarQube规则
    • "java:S2975" - 检测未实现深拷贝的clone()方法
  • IDEA插件
    • 生成DeepCopyable接口的代码模板
    • 标记未处理的可变引用字段

5. Code Review检查清单

检查项 正确做法
是否所有可变字段都被深拷贝? 递归检查引用类型字段
是否实现了Cloneable接口? 优先使用复制构造函数替代
返回的集合是否被保护? 返回不可变集合或防御性拷贝
是否误用序列化做深拷贝? 确保所有嵌套类实现Serializable

6. 真实案例

某配置中心缓存全局配置对象:

// 原始代码:浅拷贝配置对象
public AppConfig getConfig() {
    
    
    return cachedConfig.clone(); // 浅拷贝
}

// 某业务修改配置副本
AppConfig config = configService.getConfig();
config.getFeatures().put("newFeature", true); // 修改影响缓存对象

后果:所有后续请求获取的配置均包含未经验证的新特性,导致线上事故。
修复

  1. 重写clone()实现深拷贝
  2. 改用不可变配置对象
  3. 增加配置修改审核流程

总结

  • 默认clone是浅拷贝:引用字段需手动处理
  • 不可变对象是最佳防御:避免拷贝需求
  • 深拷贝有性能代价:仅在必要时使用
  • 工具不是万能的:序列化要求对象图完全可序列化

下期预告:《Spring框架下的依赖注入坑:循环依赖与作用域混淆》——从Bean创建异常到内存泄漏,直击Spring核心机制中的隐秘陷阱。

联系作者

职场经验分享,Java面试,简历修改,求职辅导尽在科技泡泡
思维导图面试视频合集
在这里插入图片描述
在这里插入图片描述