關于Java中Clonable接口和深拷貝詳解
前言
在 Java 中,Cloneable 接口是實現對象拷貝的核心機制之一,但它默認僅支持 淺拷貝;而 深拷貝 是基于淺拷貝的進階需求,用于解決引用類型成員共享的問題。
一、先搞懂:Cloneable接口是什么?
1. 核心定位:標記接口(Marker Interface)
Cloneable 是 Java 中的 標記接口(無任何抽象方法),僅用于告訴 JVM:“這個類的對象允許被克?。?/span>clone())”。
- 定義(源碼極簡):
public interface Cloneable {} // 無任何方法! - 關鍵依賴:克隆的核心實現并非來自
Cloneable接口,而是來自java.lang.Object類的clone()方法:// Object 類的 clone() 方法(protected 修飾,需重寫才能公開調用) protected native Object clone() throws CloneNotSupportedException;
2. 淺拷貝(Shallow Copy):Cloneable的默認行為
(1)淺拷貝的定義
當對象被淺拷貝時,會創(chuàng)建一個 新的對象實例,但對象中的 引用類型成員變量 不會被復制(新對象和原對象共享同一個引用類型成員)。
- 簡單說:“拷貝對象本身,不拷貝對象里的‘引用子對象’”。
(2)實現淺拷貝的3 個步驟
- 類實現
Cloneable接口(標記允許克?。?/span> - 重寫
Object類的clone()方法(提升訪問權限為public,處理異常); - 調用
super.clone()完成淺拷貝(JVM 原生實現對象拷貝)。
(3)代碼示例:淺拷貝實戰(zhàn)
// 引用類型:地址(被 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 會創(chuàng)建新對象并復制成員值
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. 創(chuàng)建原對象
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. 深拷貝的定義
深拷貝會創(chuàng)建一個 完全獨立的新對象:不僅拷貝對象本身,還會遞歸拷貝對象中所有 可變的引用類型成員變量,最終新對象和原對象的引用類型成員指向不同的內存地址,互不影響。
- 簡單說:“拷貝對象本身 + 拷貝對象里所有的‘引用子對象”。
2. 深拷貝的 3 種實現方式(實戰(zhàn)常用)
方式 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 序列化將對象寫入流(序列化),再從流中讀取(反序列化),生成的新對象是完全獨立的(無需手動遞歸拷貝)。
- 優(yōu)點:無需讓每個引用類型都實現
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. 序列化:將對象寫入字節(jié)流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 寫入當前 User 對象
// 2. 反序列化:從字節(jié)流讀取新對象
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:使用第三方工具(簡化開發(fā))
實際開發(fā)中,可借助成熟工具類避免重復編碼,常用的有:
- 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. 深拷貝的注意事項
- 循環(huán)引用處理:如果對象存在循環(huán)引用(如
A引用B,B引用A),手動克隆會棧溢出,序列化方式(或工具類)可自動處理; - 不可變類型無需深拷貝:如
String、Integer等不可變類型,淺拷貝時共享引用無問題(無法修改原值); - ** transient 關鍵字 **:序列化方式中,被
transient修飾的成員變量不會被拷貝(需根據需求決定是否使用)。
三、淺拷貝 vs 深拷貝 核心區(qū)別

四、常見面試題 & 實戰(zhàn)建議
1. 面試高頻問題
(1)Cloneable接口有什么用?如果不實現它,調用clone()會怎樣?
- 作用:標記類的對象允許被克隆(無任何方法,僅為 JVM 提供標記);
- 不實現后果:調用
super.clone()時會拋出CloneNotSupportedException(運行時異常)。
(2)Object類的clone()方法是淺拷貝還是深拷貝?
默認是 淺拷貝:僅復制對象的成員值(基本類型復制值,引用類型復制引用地址)。
(3)深拷貝的實現方式有哪些?各自的優(yōu)缺點?
- 手動克隆:優(yōu)點效率高,缺點需遞歸實現,復雜對象繁瑣;
- 序列化:優(yōu)點無需手動處理嵌套,缺點需實現
Serializable,效率略低; - 第三方工具:優(yōu)點簡潔高效,缺點依賴外部依賴。
(4)為什么String類型在淺拷貝中不會有問題?
String 是 不可變類型(一旦創(chuàng)建無法修改,修改時會生成新 String 對象),淺拷貝時共享引用不會導致原對象被修改,因此無需深拷貝。
2. 實戰(zhàn)選擇建議
- 簡單對象(無可變引用類型):用
Cloneable實現淺拷貝(高效簡潔); - 復雜對象(多層嵌套引用):優(yōu)先用第三方工具(如 Apache Commons Lang)或序列化(減少編碼量);
- 性能敏感場景:手動實現深拷貝(避免序列化的性能損耗);
- 避免過度使用克隆:如果對象可通過構造方法創(chuàng)建新實例(如
new User(user1.getName(), user1.getAge(), new Address(...))),可直接用構造方法替代克?。ǜ鬃x、無接口依賴)。
總結
- Cloneable 是 “允許克隆” 的標記接口,默認支持淺拷貝,核心依賴 Object.clone();
- 淺拷貝適合簡單對象,深拷貝解決引用類型共享問題,需通過手動遞歸、序列化或工具類實現;
- 實際開發(fā)中,優(yōu)先選擇第三方工具(如 SerializationUtils)實現深拷貝,兼顧簡潔性和穩(wěn)定性。
到此這篇關于關于Java中Clonable接口和深拷貝的文章就介紹到這了,更多相關Java中Clonable接口和深拷貝內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java之SpringCloudAlibaba Sentinel組件案例講解
這篇文章主要介紹了Java之SpringCloudAlibaba Sentinel組件案例講解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07
解析Arthas協(xié)助排查線上skywalking不可用問題
這篇文章主要為大家介紹了解析Arthas協(xié)助排查線上skywalking不可用的問題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-02-02
springboot項目中引入本地依賴jar包并打包到lib文件夾中
這篇文章主要介紹了springboot項目中引入本地依賴jar包,如何打包到lib文件夾中,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04

