#浅拷贝与深拷贝
面试官问:"浅拷贝和深拷贝有什么区别?"
候选人小冯答:"浅拷贝只复制引用,深拷贝复制整个对象。"
面试官点点头:"那 Java 怎么实现深拷贝?"
小冯说:"可以用 clone() 方法,或者序列化..."
面试官追问:"如果对象里有引用类型,怎么保证 clone() 是深拷贝而不是浅拷贝?"
小冯答不上来。
【面试官心理】 这道题考查的是候选人对对象复制机制的理解深度。能说出 clone() 方法需要手动处理引用字段的候选人,说明真正理解过浅拷贝和深拷贝的区别。
#一、基本概念 🔴
#1.1 浅拷贝(Shallow Copy)
class Address {
String city;
Address(String city) { this.city = city; }
}
class Person implements Cloneable {
String name;
Address address; // 引用类型
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() {
try {
return (Person) super.clone(); // 默认是浅拷贝
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Person p1 = new Person("Alice", new Address("Beijing"));
Person p2 = p1.clone();
// 浅拷贝结果:
// p1.name = "Alice", p1.address = Address@123
// p2.name = "Alice", p2.address = Address@123 ← 同一个对象!
// 修改 p2 的引用类型字段会影响 p1:
p2.address.city = "Shanghai";
System.out.println(p1.address.city); // Shanghai ← 被影响了!#1.2 深拷贝(Deep Copy)
class Person implements Cloneable {
String name;
Address address;
@Override
protected Person clone() {
try {
Person cloned = (Person) super.clone();
// ✅ 深拷贝:手动克隆引用字段
cloned.address = this.address.clone();
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
// 深拷贝结果:
// p1.address = Address@123
// p2.address = Address@456 ← 新对象!
// 修改 p2 的引用类型字段不影响 p1:
p2.address.city = "Shanghai";
System.out.println(p1.address.city); // Beijing ← 没被影响#二、Cloneable 接口的局限性 🔴
#2.1 Cloneable 的设计缺陷
// Cloneable 是一个"标记接口",没有任何方法
// 它的问题是:clone() 方法不是 Cloneable 定义的!
public interface Cloneable { }
// Object.clone() 是这样工作的:
// protected native Object clone() throws CloneNotSupportedException;
// 只有实现了 Cloneable,Object.clone() 才返回字段拷贝
// 否则抛 CloneNotSupportedException
class BadClass { }
BadClass obj = new BadClass();
obj.clone(); // ❌ CloneNotSupportedException(没有实现 Cloneable)#2.2 clone() 的坑
// 坑一:返回 Object,需要强制类型转换
Person p = (Person) super.clone();
// 坑二:必须处理受检异常
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
// ...
}
// 坑三:final 字段无法深拷贝
class Person implements Cloneable {
String name;
final Address address = new Address(); // final 字段
@Override
protected Person clone() {
Person cloned = (Person) super.clone();
// ❌ 无法为 final 字段重新赋值
// cloned.address = this.address.clone(); // 编译错误
return cloned;
}
}
// 坑四:引用链深层拷贝
class A {
B b;
}
class B {
C c;
}
class C {
String data;
}
// 深拷贝 A 需要同时克隆 B 和 C,一层层嵌套#三、深拷贝的常用方式 🔴
#3.1 手动克隆(需要所有类配合)
class Address implements Cloneable {
String city;
@Override
protected Address clone() { return new Address(this.city); }
}
class Person implements Cloneable {
String name;
Address address;
@Override
protected Person clone() {
Person cloned = (Person) super.clone();
cloned.address = this.address.clone();
return cloned;
}
}#3.2 序列化(JSON / Java Serializable)
// 方式一:JSON 序列化(推荐)
public static <T> T deepCopy(T obj) {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(mapper.writeValueAsString(obj), (Class<T>) obj.getClass());
}
// 方式二:Java 序列化
public static <T extends Serializable> T deepCopy(T obj) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
@SuppressWarnings("unchecked")
T copy = (T) ois.readObject();
return copy;
} catch (Exception e) {
throw new IllegalArgumentException("Clone failed", e);
}
}#3.3 Apache Commons 工具
// Apache Commons Lang
public static <T> T deepClone(T obj) {
try {
byte[] bytes = SerializationUtils.serialize((Serializable) obj);
return (T) SerializationUtils.deserialize(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Apache Commons BeanUtils
BeanUtils.cloneProperties(target, source); // 浅拷贝
BeanUtils.copyProperties(target, source); // 浅拷贝
// 深拷贝需要自定义转换器#3.4 Google Guava 工具
// Guava 的深拷贝
public static <T> T deepCopy(T obj) {
return new Gson().fromJson(new Gson().toJson(obj), obj.getClass());
}#四、性能对比 🟡
| 方式 | 性能 | 依赖 | 限制 |
|---|---|---|---|
| 手动 clone | 最快 | 所有类实现 Cloneable | 深层对象复杂 |
| JSON 序列化 | 慢(序列化开销) | Jackson/Gson | 需要无参构造器 |
| Java 序列化 | 慢 | JDK 内置 | 所有对象实现 Serializable |
| Apache Commons | 中等 | commons-lang | 需要 Serializable |
#五、生产中的选择 🟡
// 场景一:DTO 拷贝(字段简单)
UserDTO dto = modelMapper.map(user, UserDTO.class);
// 场景二:复杂对象图
Person deepCopy = new Gson().fromJson(new Gson().toJson(person), Person.class);
// 场景三:需要高性能
// 使用手动 clone() 或 proto-protobuf 等高性能序列化
// 场景四:需要保留对象关系
// 例如:树形结构节点间有父子引用
// 深拷贝时需要手动维护引用关系#六、追问升级
面试官:"为什么阿里巴巴 Java 规约不推荐使用 clone() 方法?"
// 阿里巴巴 Java 开发规约:不推荐使用 clone()
// 原因:
// 1. clone() 是 Object 的 protected 方法,调用不便
// 2. CloneNotSupportedException 是受检异常
// 3. 字段逐个拷贝,不够灵活
// 4. 无法拷贝 final 字段
// 5. 类型安全问题(强制类型转换)
// ✅ 推荐的替代方案:
// 1. 构造器拷贝
new Person(existing.getName(), existing.getAddress().clone())
// 2. 工厂方法
Person.from(other)
// 3. 序列化工具
SerializationUtils.clone(obj)【面试官心理】 能说出阿里巴巴规约不推荐 clone() 的原因的候选人,说明有代码规范意识,并且理解过替代方案。这是 P6 的要求。