关于Java中Clonable接口和深拷贝详解
作者:Porunarufu
前言
在 Java 中,Cloneable 接口是实现对象拷贝的核心机制之一,但它默认仅支持 浅拷贝;而 深拷贝 是基于浅拷贝的进阶需求,用于解决引用类型成员共享的问题。
一、先搞懂:Cloneable接口是什么?
1. 核心定位:标记接口(Marker Interface)
Cloneable 是 Java 中的 标记接口(无任何抽象方法),仅用于告诉 JVM:“这个类的对象允许被克隆(clone())”。
- 定义(源码极简):
public interface Cloneable {} // 无任何方法! - 关键依赖:克隆的核心实现并非来自
Cloneable接口,而是来自java.lang.Object类的clone()方法:// Object 类的 clone() 方法(protected 修饰,需重写才能公开调用) protected native Object clone() throws CloneNotSupportedException;
2. 浅拷贝(Shallow Copy):Cloneable的默认行为
(1)浅拷贝的定义
当对象被浅拷贝时,会创建一个 新的对象实例,但对象中的 引用类型成员变量 不会被复制(新对象和原对象共享同一个引用类型成员)。
- 简单说:“拷贝对象本身,不拷贝对象里的‘引用子对象’”。
(2)实现浅拷贝的3 个步骤
- 类实现
Cloneable接口(标记允许克隆); - 重写
Object类的clone()方法(提升访问权限为public,处理异常); - 调用
super.clone()完成浅拷贝(JVM 原生实现对象拷贝)。
(3)代码示例:浅拷贝实战
// 引用类型:地址(被 User 类引用)
class Address {
private String city;
public Address(String city) {
this.city = city;
}
// getter/setter
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
}
// 实现 Cloneable 接口,支持浅拷贝
class User implements Cloneable {
private String name; // 基本类型包装类(不可变,浅拷贝无问题)
private int age; // 基本类型(浅拷贝直接复制值)
private Address addr;// 引用类型(浅拷贝仅复制引用,共享对象)
// 构造方法
public User(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 重写 clone() 方法:实现浅拷贝
@Override
public User clone() throws CloneNotSupportedException {
// 调用 Object 的 clone(),JVM 会创建新对象并复制成员值
return (User) super.clone();
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Address getAddr() { return addr; }
}
// 测试浅拷贝
public class ShallowCopyTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原对象
Address addr = new Address("北京");
User user1 = new User("张三", 20, addr);
// 2. 浅拷贝得到新对象
User user2 = user1.clone();
// 3. 验证:基本类型/不可变类型的拷贝(独立)
System.out.println(user1.getName() == user2.getName()); // true(String 不可变,共享无问题)
user2.setName("李四");
user2.setAge(22);
System.out.println(user1.getName()); // 张三(原对象不受影响)
System.out.println(user1.getAge()); // 20(原对象不受影响)
// 4. 验证:引用类型的拷贝(共享)
System.out.println(user1.getAddr() == user2.getAddr()); // true(同一个 Address 对象)
user2.getAddr().setCity("上海"); // 修改 user2 的 Address
System.out.println(user1.getAddr().getCity()); // 上海(原对象的 Address 也被修改!)
}
}(4)浅拷贝的问题
当对象包含 可变的引用类型成员(如 Address)时,浅拷贝会导致原对象和拷贝对象共享该成员,修改其中一个会影响另一个,破坏对象的 “独立性”—— 这就是需要深拷贝的场景。
二、深拷贝(Deep Copy):解决引用类型共享问题
1. 深拷贝的定义
深拷贝会创建一个 完全独立的新对象:不仅拷贝对象本身,还会递归拷贝对象中所有 可变的引用类型成员变量,最终新对象和原对象的引用类型成员指向不同的内存地址,互不影响。
- 简单说:“拷贝对象本身 + 拷贝对象里所有的‘引用子对象”。
2. 深拷贝的 3 种实现方式(实战常用)
方式 1:重写clone()方法,手动递归拷贝引用类型
核心思路:在浅拷贝的基础上,对每个引用类型成员也调用其 clone() 方法(需让引用类型也实现 Cloneable)。
// 步骤1:让引用类型 Address 也实现 Cloneable,支持拷贝
class Address implements Cloneable {
private String city;
public Address(String city) { this.city = city; }
// 重写 clone() 方法
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
// getter/setter 略
}
// 步骤2:User 类的 clone() 中,手动拷贝 Address
class User implements Cloneable {
private String name;
private int age;
private Address addr;
// 构造方法略
@Override
public User clone() throws CloneNotSupportedException {
// 第一步:浅拷贝 User 对象本身
User newUser = (User) super.clone();
// 第二步:手动拷贝引用类型成员(深拷贝核心)
newUser.addr = this.addr.clone(); // 递归拷贝 Address
return newUser;
}
// getter/setter 略
}
// 测试深拷贝
public class DeepCopyTest1 {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
User user1 = new User("张三", 20, addr);
User user2 = user1.clone();
// 验证:引用类型成员不再共享
System.out.println(user1.getAddr() == user2.getAddr()); // false(不同 Address 对象)
user2.getAddr().setCity("上海");
System.out.println(user1.getAddr().getCity()); // 北京(原对象不受影响)
System.out.println(user2.getAddr().getCity()); // 上海(新对象独立修改)
}
}方式 2:通过序列化(Serializable)实现深拷贝
核心思路:利用 Java 序列化将对象写入流(序列化),再从流中读取(反序列化),生成的新对象是完全独立的(无需手动递归拷贝)。
- 优点:无需让每个引用类型都实现
Cloneable,适合复杂对象(多层引用嵌套); - 缺点:需要所有成员变量都实现
Serializable接口,效率略低于手动克隆。
import java.io.*;
// 步骤1:所有相关类实现 Serializable 接口(标记可序列化)
class Address implements Serializable {
private String city;
public Address(String city) { this.city = city; }
// getter/setter 略
}
class User implements Serializable {
private String name;
private int age;
private Address addr;
public User(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 步骤2:实现深拷贝工具方法(序列化+反序列化)
public User deepCopy() throws IOException, ClassNotFoundException {
// 1. 序列化:将对象写入字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 写入当前 User 对象
// 2. 反序列化:从字节流读取新对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject(); // 生成独立的新对象
}
// getter/setter 略
}
// 测试序列化深拷贝
public class DeepCopyTest2 {
public static void main(String[] args) throws Exception {
Address addr = new Address("北京");
User user1 = new User("张三", 20, addr);
User user2 = user1.deepCopy();
// 验证:完全独立
System.out.println(user1.getAddr() == user2.getAddr()); // false
user2.getAddr().setCity("上海");
System.out.println(user1.getAddr().getCity()); // 北京
System.out.println(user2.getAddr().getCity()); // 上海
}
}方式 3:使用第三方工具(简化开发)
实际开发中,可借助成熟工具类避免重复编码,常用的有:
- Apache Commons Lang:
SerializationUtils.clone()(基于序列化,无需手动写流操作); - Gson/Jackson:将对象转为 JSON 字符串,再转回对象(间接实现深拷贝,支持复杂对象)。
示例(Apache Commons Lang):
- 引入依赖(Maven):
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency> - 代码实现:
import org.apache.commons.lang3.SerializationUtils; // 所有类仍需实现 Serializable class User implements Serializable { /* 成员变量、构造方法略 */ } public class DeepCopyTest3 { public static void main(String[] args) { Address addr = new Address("北京"); User user1 = new User("张三", 20, addr); // 一行代码实现深拷贝 User user2 = SerializationUtils.clone(user1); System.out.println(user1.getAddr() == user2.getAddr()); // false } }
3. 深拷贝的注意事项
- 循环引用处理:如果对象存在循环引用(如
A引用B,B引用A),手动克隆会栈溢出,序列化方式(或工具类)可自动处理; - 不可变类型无需深拷贝:如
String、Integer等不可变类型,浅拷贝时共享引用无问题(无法修改原值); - ** transient 关键字 **:序列化方式中,被
transient修饰的成员变量不会被拷贝(需根据需求决定是否使用)。
三、浅拷贝 vs 深拷贝 核心区别

四、常见面试题 & 实战建议
1. 面试高频问题
(1)Cloneable接口有什么用?如果不实现它,调用clone()会怎样?
- 作用:标记类的对象允许被克隆(无任何方法,仅为 JVM 提供标记);
- 不实现后果:调用
super.clone()时会抛出CloneNotSupportedException(运行时异常)。
(2)Object类的clone()方法是浅拷贝还是深拷贝?
默认是 浅拷贝:仅复制对象的成员值(基本类型复制值,引用类型复制引用地址)。
(3)深拷贝的实现方式有哪些?各自的优缺点?
- 手动克隆:优点效率高,缺点需递归实现,复杂对象繁琐;
- 序列化:优点无需手动处理嵌套,缺点需实现
Serializable,效率略低; - 第三方工具:优点简洁高效,缺点依赖外部依赖。
(4)为什么String类型在浅拷贝中不会有问题?
String 是 不可变类型(一旦创建无法修改,修改时会生成新 String 对象),浅拷贝时共享引用不会导致原对象被修改,因此无需深拷贝。
2. 实战选择建议
- 简单对象(无可变引用类型):用
Cloneable实现浅拷贝(高效简洁); - 复杂对象(多层嵌套引用):优先用第三方工具(如 Apache Commons Lang)或序列化(减少编码量);
- 性能敏感场景:手动实现深拷贝(避免序列化的性能损耗);
- 避免过度使用克隆:如果对象可通过构造方法创建新实例(如
new User(user1.getName(), user1.getAge(), new Address(...))),可直接用构造方法替代克隆(更易读、无接口依赖)。
总结
- Cloneable 是 “允许克隆” 的标记接口,默认支持浅拷贝,核心依赖 Object.clone();
- 浅拷贝适合简单对象,深拷贝解决引用类型共享问题,需通过手动递归、序列化或工具类实现;
- 实际开发中,优先选择第三方工具(如 SerializationUtils)实现深拷贝,兼顾简洁性和稳定性。
到此这篇关于关于Java中Clonable接口和深拷贝的文章就介绍到这了,更多相关Java中Clonable接口和深拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
