浅拷贝与深拷贝详解:概念、代码示例与后端应用场景

目录

一、浅谈深拷贝和浅拷贝

1、拷贝对象

1.浅拷贝 (Shallow Copy)

2.深拷贝 (Deep Copy)

2、为什么要使用深拷贝

3、代码验证目录

1.cloneable接口

2.浅拷贝示例

3.深拷贝示例

二、浅拷贝和深拷贝的使用场景

[1、浅拷贝在 Web 后端的应用](#1、浅拷贝在 Web 后端的应用)

[1.DTO ↔ Entity 转换](#1.DTO ↔ Entity 转换)

[2、深拷贝在 Web 后端的应用](#2、深拷贝在 Web 后端的应用)

[1. 数据快照 / 历史归档](#1. 数据快照 / 历史归档)

[2. 并发安全](#2. 并发安全)

一、浅谈深拷贝和浅拷贝

1、拷贝对象

对象拷贝(Object Copy)指将一个对象的属性复制到另一个类型相同的对象中。

在编程中,拷贝对象非常常见,通常是为了在新的环境中复用已有对象的数据。

1.浅拷贝 (Shallow Copy)

浅拷贝仅复制对象的引用,而不是对象本身。新旧对象共享同一块内存区域,修改其中一个对象会影响另一个。

拷贝基本类型

直接复制基本数据类型的值。

拷贝引用类型

仅复制引用地址,因此修改一个对象的数据会影响另一个对象。

总结

多个引用指向同一对象,修改其中一个会影响所有引用该对象的地方。

2.深拷贝 (Deep Copy)

深拷贝会创建一个新的对象,且新旧对象不共享内存,修改新对象不会影响原对象。

深拷贝复制源对象的所有属性,包括所有引用类型的对象,因此源对象和拷贝对象完全独立。

总结

深拷贝创建了一个完全独立的新对象,源对象和新对象之间没有任何引用关系。

2、为什么要使用深拷贝

避免共享引用

浅拷贝只是复制引用,可能导致修改副本时影响原对象。深拷贝确保创建独立副本,避免这种问题。

线程安全

在多线程环境中,多个线程可能同时修改同一个对象,造成数据冲突。使用深拷贝可以让每个线程操作独立副本,从而避免线程安全问题。

3、代码验证目录

1.cloneable接口

源码

知识点

如果类没有实现 Cloneable 接口,调用 clone() 方法时会抛出异常。

需要实现 Cloneable 接口后,才能正常调用 clone() 方法。

代码案例

2.浅拷贝示例

java

复制代码

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

public class ShallowDeepCopyDemo

{

public static void main(String[] args) throws CloneNotSupportedException

{

m1();

}

private static void m1() throws CloneNotSupportedException

{

Emp emp = new Emp("z3", 15, "雷军", "CEO");

System.out.println("原始对象:" + emp.getBoss().getTitle());

Emp emp2 = (Emp) emp.clone();

System.out.println("拷贝对象:" + emp2.getBoss().getTitle());

System.out.println();

emp2.getBoss().setTitle("CTO");

System.out.println("------emp2拷贝对象修改Title=CTO,是否会影响原始对象");

System.out.println("原始对象:" + emp.getBoss().getTitle());

System.out.println("拷贝对象:" + emp2.getBoss().getTitle());

}

}

@Data

@AllArgsConstructor

@NoArgsConstructor

class Boss implements Cloneable

{

private String bossName;

private String title;

@Override

protected Object clone() throws CloneNotSupportedException

{

return super.clone();

}

}

@Data

@AllArgsConstructor

@NoArgsConstructor

class Emp implements Cloneable

{

private String empName;

private Integer age;

private Boss boss;

public Emp(String empName, Integer age, String bossName, String title)

{

this.empName = empName;

this.age = age;

this.boss = new Boss(bossName, title);

}

@Override

protected Object clone() throws CloneNotSupportedException

{

return super.clone();

}

}

3.深拷贝示例

修改Emp

当使用深拷贝,值就被修改了

二、浅拷贝和深拷贝的使用场景

1、浅拷贝在 Web 后端的应用

浅拷贝一般用在 对象之间的属性复制 或 轻量数据传输,因为它快、开销小。

1.DTO ↔ Entity 转换

在 Controller 层收到请求 DTO,需要转成 Entity 保存数据库时,常用浅拷贝工具类:

java

复制代码

import org.springframework.beans.BeanUtils;

UserDTO dto = new UserDTO("Alice", 20);

UserEntity entity = new UserEntity();

BeanUtils.copyProperties(dto, entity); // 浅拷贝

System.out.println(entity.getName()); // Alice

👉 常见于:

Request → Entity

Entity → ResponseVO

1. 浅拷贝的本质

浅拷贝做的事情就是:

创建一个 新的外层对象 (UserEntity)。

把源对象(UserDTO)的字段值逐个赋值给目标对象的字段。

如果字段是基本类型 / 不可变对象 (int, long, String, Integer...) → 拷贝的是值。

如果字段是可变引用对象 (List, Map, 自定义对象) → 拷贝的是引用。

所以,BeanUtils.copyProperties(dto, entity) 只是"复制了一层属性",不会深度复制内部引用对象。

2. 为什么 DTO ↔ Entity 常用浅拷贝

我们来看常见代码:

java

复制代码

UserDTO dto = new UserDTO("Alice", 20);

UserEntity entity = new UserEntity();

BeanUtils.copyProperties(dto, entity);

System.out.println(entity.getName()); // Alice

这里 name 是 String,age 是 int:

String 是不可变对象,即便两个对象指向同一个字符串引用也没问题。

int 是基本类型,直接复制值,完全独立。

所以这种场景下,浅拷贝就够了。

3. 什么时候浅拷贝会出问题?

假设 DTO 里有个集合字段:

java

复制代码

class UserDTO {

private String name;

private List roles;

}

class UserEntity {

private String name;

private List roles;

}

代码:

java

复制代码

UserDTO dto = new UserDTO();

dto.setName("Alice");

dto.setRoles(new ArrayList<>(List.of("ADMIN")));

UserEntity entity = new UserEntity();

BeanUtils.copyProperties(dto, entity);

entity.getRoles().add("USER");

System.out.println(dto.getRoles()); // ❌ ["ADMIN", "USER"] 也被改了

👉 因为 roles 是可变对象,浅拷贝后 dto.roles 和 entity.roles 指向同一个 List。

4. 为什么大部分场景没问题

在 Request → Entity 时,通常 DTO 里字段是简单类型(String, int),浅拷贝足够。

在 Entity → ResponseVO 时,也主要是简单类型(id, name, age, status),浅拷贝完全满足需求。

很少会直接把复杂的集合对象在 DTO 和 Entity 之间原样拷贝。即便有,也常常在 Service 层做 额外转换(比如 MapStruct、手写转换逻辑)。

5. 总结

BeanUtils.copyProperties 是 浅拷贝:只复制属性,不递归复制引用对象。

在 DTO ↔ Entity 里,字段多为简单类型,所以浅拷贝完全够用。

如果有集合或嵌套对象,浅拷贝会共享引用,可能导致数据串改 → 这时要么手动处理,要么用 MapStruct 这种强类型映射工具。

2、深拷贝在 Web 后端的应用

深拷贝适合 数据快照 、并发安全 、防止数据污染 的场景。

1. 数据快照 / 历史归档

例如电商订单支付前,需要保存一份"冻结快照":

java

复制代码

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

Order orderSnapshot = mapper.readValue(mapper.writeValueAsString(order), Order.class); // JSON 深拷贝

orderSnapshot.setStatus("FROZEN");

👉 这样修改快照不会影响到原始订单。

2. 并发安全

多线程处理时,如果多个线程共享对象,必须深拷贝避免互相污染:

java

复制代码

Order original = orderService.getOrderById(1001);

Order threadOrder = (Order) SerializationUtils.clone(original); // Apache Commons 深拷贝

// 各线程独立处理自己的对象