關(guān)于Java中Clonable接口和深拷貝詳解
前言
在 Java 中,Cloneable 接口是實(shí)現(xiàn)對(duì)象拷貝的核心機(jī)制之一,但它默認(rèn)僅支持 淺拷貝;而 深拷貝 是基于淺拷貝的進(jìn)階需求,用于解決引用類(lèi)型成員共享的問(wèn)題。
一、先搞懂:Cloneable接口是什么?
1. 核心定位:標(biāo)記接口(Marker Interface)
Cloneable 是 Java 中的 標(biāo)記接口(無(wú)任何抽象方法),僅用于告訴 JVM:“這個(gè)類(lèi)的對(duì)象允許被克隆(clone())”。
- 定義(源碼極簡(jiǎn)):
public interface Cloneable {} // 無(wú)任何方法! - 關(guān)鍵依賴:克隆的核心實(shí)現(xiàn)并非來(lái)自
Cloneable接口,而是來(lái)自java.lang.Object類(lèi)的clone()方法:// Object 類(lèi)的 clone() 方法(protected 修飾,需重寫(xiě)才能公開(kāi)調(diào)用) protected native Object clone() throws CloneNotSupportedException;
2. 淺拷貝(Shallow Copy):Cloneable的默認(rèn)行為
(1)淺拷貝的定義
當(dāng)對(duì)象被淺拷貝時(shí),會(huì)創(chuàng)建一個(gè) 新的對(duì)象實(shí)例,但對(duì)象中的 引用類(lèi)型成員變量 不會(huì)被復(fù)制(新對(duì)象和原對(duì)象共享同一個(gè)引用類(lèi)型成員)。
- 簡(jiǎn)單說(shuō):“拷貝對(duì)象本身,不拷貝對(duì)象里的‘引用子對(duì)象’”。
(2)實(shí)現(xiàn)淺拷貝的3 個(gè)步驟
- 類(lèi)實(shí)現(xiàn)
Cloneable接口(標(biāo)記允許克?。?; - 重寫(xiě)
Object類(lèi)的clone()方法(提升訪問(wèn)權(quán)限為public,處理異常); - 調(diào)用
super.clone()完成淺拷貝(JVM 原生實(shí)現(xiàn)對(duì)象拷貝)。
(3)代碼示例:淺拷貝實(shí)戰(zhàn)
// 引用類(lèi)型:地址(被 User 類(lèi)引用)
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; }
}
// 實(shí)現(xiàn) Cloneable 接口,支持淺拷貝
class User implements Cloneable {
private String name; // 基本類(lèi)型包裝類(lèi)(不可變,淺拷貝無(wú)問(wèn)題)
private int age; // 基本類(lèi)型(淺拷貝直接復(fù)制值)
private Address addr;// 引用類(lèi)型(淺拷貝僅復(fù)制引用,共享對(duì)象)
// 構(gòu)造方法
public User(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 重寫(xiě) clone() 方法:實(shí)現(xiàn)淺拷貝
@Override
public User clone() throws CloneNotSupportedException {
// 調(diào)用 Object 的 clone(),JVM 會(huì)創(chuàng)建新對(duì)象并復(fù)制成員值
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; }
}
// 測(cè)試淺拷貝
public class ShallowCopyTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 創(chuàng)建原對(duì)象
Address addr = new Address("北京");
User user1 = new User("張三", 20, addr);
// 2. 淺拷貝得到新對(duì)象
User user2 = user1.clone();
// 3. 驗(yàn)證:基本類(lèi)型/不可變類(lèi)型的拷貝(獨(dú)立)
System.out.println(user1.getName() == user2.getName()); // true(String 不可變,共享無(wú)問(wèn)題)
user2.setName("李四");
user2.setAge(22);
System.out.println(user1.getName()); // 張三(原對(duì)象不受影響)
System.out.println(user1.getAge()); // 20(原對(duì)象不受影響)
// 4. 驗(yàn)證:引用類(lèi)型的拷貝(共享)
System.out.println(user1.getAddr() == user2.getAddr()); // true(同一個(gè) Address 對(duì)象)
user2.getAddr().setCity("上海"); // 修改 user2 的 Address
System.out.println(user1.getAddr().getCity()); // 上海(原對(duì)象的 Address 也被修改?。?
}
}(4)淺拷貝的問(wèn)題
當(dāng)對(duì)象包含 可變的引用類(lèi)型成員(如 Address)時(shí),淺拷貝會(huì)導(dǎo)致原對(duì)象和拷貝對(duì)象共享該成員,修改其中一個(gè)會(huì)影響另一個(gè),破壞對(duì)象的 “獨(dú)立性”—— 這就是需要深拷貝的場(chǎng)景。
二、深拷貝(Deep Copy):解決引用類(lèi)型共享問(wèn)題
1. 深拷貝的定義
深拷貝會(huì)創(chuàng)建一個(gè) 完全獨(dú)立的新對(duì)象:不僅拷貝對(duì)象本身,還會(huì)遞歸拷貝對(duì)象中所有 可變的引用類(lèi)型成員變量,最終新對(duì)象和原對(duì)象的引用類(lèi)型成員指向不同的內(nèi)存地址,互不影響。
- 簡(jiǎn)單說(shuō):“拷貝對(duì)象本身 + 拷貝對(duì)象里所有的‘引用子對(duì)象”。
2. 深拷貝的 3 種實(shí)現(xiàn)方式(實(shí)戰(zhàn)常用)
方式 1:重寫(xiě)clone()方法,手動(dòng)遞歸拷貝引用類(lèi)型
核心思路:在淺拷貝的基礎(chǔ)上,對(duì)每個(gè)引用類(lèi)型成員也調(diào)用其 clone() 方法(需讓引用類(lèi)型也實(shí)現(xiàn) Cloneable)。
// 步驟1:讓引用類(lèi)型 Address 也實(shí)現(xiàn) Cloneable,支持拷貝
class Address implements Cloneable {
private String city;
public Address(String city) { this.city = city; }
// 重寫(xiě) clone() 方法
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
// getter/setter 略
}
// 步驟2:User 類(lèi)的 clone() 中,手動(dòng)拷貝 Address
class User implements Cloneable {
private String name;
private int age;
private Address addr;
// 構(gòu)造方法略
@Override
public User clone() throws CloneNotSupportedException {
// 第一步:淺拷貝 User 對(duì)象本身
User newUser = (User) super.clone();
// 第二步:手動(dòng)拷貝引用類(lèi)型成員(深拷貝核心)
newUser.addr = this.addr.clone(); // 遞歸拷貝 Address
return newUser;
}
// getter/setter 略
}
// 測(cè)試深拷貝
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();
// 驗(yàn)證:引用類(lèi)型成員不再共享
System.out.println(user1.getAddr() == user2.getAddr()); // false(不同 Address 對(duì)象)
user2.getAddr().setCity("上海");
System.out.println(user1.getAddr().getCity()); // 北京(原對(duì)象不受影響)
System.out.println(user2.getAddr().getCity()); // 上海(新對(duì)象獨(dú)立修改)
}
}方式 2:通過(guò)序列化(Serializable)實(shí)現(xiàn)深拷貝
核心思路:利用 Java 序列化將對(duì)象寫(xiě)入流(序列化),再?gòu)牧髦凶x?。ǚ葱蛄谢?/span>,生成的新對(duì)象是完全獨(dú)立的(無(wú)需手動(dòng)遞歸拷貝)。
- 優(yōu)點(diǎn):無(wú)需讓每個(gè)引用類(lèi)型都實(shí)現(xiàn)
Cloneable,適合復(fù)雜對(duì)象(多層引用嵌套); - 缺點(diǎn):需要所有成員變量都實(shí)現(xiàn)
Serializable接口,效率略低于手動(dòng)克隆。
import java.io.*;
// 步驟1:所有相關(guān)類(lèi)實(shí)現(xiàn) Serializable 接口(標(biāo)記可序列化)
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:實(shí)現(xiàn)深拷貝工具方法(序列化+反序列化)
public User deepCopy() throws IOException, ClassNotFoundException {
// 1. 序列化:將對(duì)象寫(xiě)入字節(jié)流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 寫(xiě)入當(dāng)前 User 對(duì)象
// 2. 反序列化:從字節(jié)流讀取新對(duì)象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject(); // 生成獨(dú)立的新對(duì)象
}
// getter/setter 略
}
// 測(cè)試序列化深拷貝
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();
// 驗(yàn)證:完全獨(dú)立
System.out.println(user1.getAddr() == user2.getAddr()); // false
user2.getAddr().setCity("上海");
System.out.println(user1.getAddr().getCity()); // 北京
System.out.println(user2.getAddr().getCity()); // 上海
}
}方式 3:使用第三方工具(簡(jiǎn)化開(kāi)發(fā))
實(shí)際開(kāi)發(fā)中,可借助成熟工具類(lèi)避免重復(fù)編碼,常用的有:
- Apache Commons Lang:
SerializationUtils.clone()(基于序列化,無(wú)需手動(dòng)寫(xiě)流操作); - Gson/Jackson:將對(duì)象轉(zhuǎn)為 JSON 字符串,再轉(zhuǎn)回對(duì)象(間接實(shí)現(xiàn)深拷貝,支持復(fù)雜對(duì)象)。
示例(Apache Commons Lang):
- 引入依賴(Maven):
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency> - 代碼實(shí)現(xiàn):
import org.apache.commons.lang3.SerializationUtils; // 所有類(lèi)仍需實(shí)現(xiàn) Serializable class User implements Serializable { /* 成員變量、構(gòu)造方法略 */ } public class DeepCopyTest3 { public static void main(String[] args) { Address addr = new Address("北京"); User user1 = new User("張三", 20, addr); // 一行代碼實(shí)現(xiàn)深拷貝 User user2 = SerializationUtils.clone(user1); System.out.println(user1.getAddr() == user2.getAddr()); // false } }
3. 深拷貝的注意事項(xiàng)
- 循環(huán)引用處理:如果對(duì)象存在循環(huán)引用(如
A引用B,B引用A),手動(dòng)克隆會(huì)棧溢出,序列化方式(或工具類(lèi))可自動(dòng)處理; - 不可變類(lèi)型無(wú)需深拷貝:如
String、Integer等不可變類(lèi)型,淺拷貝時(shí)共享引用無(wú)問(wèn)題(無(wú)法修改原值); - ** transient 關(guān)鍵字 **:序列化方式中,被
transient修飾的成員變量不會(huì)被拷貝(需根據(jù)需求決定是否使用)。
三、淺拷貝 vs 深拷貝 核心區(qū)別

四、常見(jiàn)面試題 & 實(shí)戰(zhàn)建議
1. 面試高頻問(wèn)題
(1)Cloneable接口有什么用?如果不實(shí)現(xiàn)它,調(diào)用clone()會(huì)怎樣?
- 作用:標(biāo)記類(lèi)的對(duì)象允許被克隆(無(wú)任何方法,僅為 JVM 提供標(biāo)記);
- 不實(shí)現(xiàn)后果:調(diào)用
super.clone()時(shí)會(huì)拋出CloneNotSupportedException(運(yùn)行時(shí)異常)。
(2)Object類(lèi)的clone()方法是淺拷貝還是深拷貝?
默認(rèn)是 淺拷貝:僅復(fù)制對(duì)象的成員值(基本類(lèi)型復(fù)制值,引用類(lèi)型復(fù)制引用地址)。
(3)深拷貝的實(shí)現(xiàn)方式有哪些?各自的優(yōu)缺點(diǎn)?
- 手動(dòng)克隆:優(yōu)點(diǎn)效率高,缺點(diǎn)需遞歸實(shí)現(xiàn),復(fù)雜對(duì)象繁瑣;
- 序列化:優(yōu)點(diǎn)無(wú)需手動(dòng)處理嵌套,缺點(diǎn)需實(shí)現(xiàn)
Serializable,效率略低; - 第三方工具:優(yōu)點(diǎn)簡(jiǎn)潔高效,缺點(diǎn)依賴外部依賴。
(4)為什么String類(lèi)型在淺拷貝中不會(huì)有問(wèn)題?
String 是 不可變類(lèi)型(一旦創(chuàng)建無(wú)法修改,修改時(shí)會(huì)生成新 String 對(duì)象),淺拷貝時(shí)共享引用不會(huì)導(dǎo)致原對(duì)象被修改,因此無(wú)需深拷貝。
2. 實(shí)戰(zhàn)選擇建議
- 簡(jiǎn)單對(duì)象(無(wú)可變引用類(lèi)型):用
Cloneable實(shí)現(xiàn)淺拷貝(高效簡(jiǎn)潔); - 復(fù)雜對(duì)象(多層嵌套引用):優(yōu)先用第三方工具(如 Apache Commons Lang)或序列化(減少編碼量);
- 性能敏感場(chǎng)景:手動(dòng)實(shí)現(xiàn)深拷貝(避免序列化的性能損耗);
- 避免過(guò)度使用克隆:如果對(duì)象可通過(guò)構(gòu)造方法創(chuàng)建新實(shí)例(如
new User(user1.getName(), user1.getAge(), new Address(...))),可直接用構(gòu)造方法替代克?。ǜ鬃x、無(wú)接口依賴)。
總結(jié)
- Cloneable 是 “允許克隆” 的標(biāo)記接口,默認(rèn)支持淺拷貝,核心依賴 Object.clone();
- 淺拷貝適合簡(jiǎn)單對(duì)象,深拷貝解決引用類(lèi)型共享問(wèn)題,需通過(guò)手動(dòng)遞歸、序列化或工具類(lèi)實(shí)現(xiàn);
- 實(shí)際開(kāi)發(fā)中,優(yōu)先選擇第三方工具(如 SerializationUtils)實(shí)現(xiàn)深拷貝,兼顧簡(jiǎn)潔性和穩(wěn)定性。
到此這篇關(guān)于關(guān)于Java中Clonable接口和深拷貝的文章就介紹到這了,更多相關(guān)Java中Clonable接口和深拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中的ArrayList(擴(kuò)容機(jī)制)詳解
ArrayList作為Java中廣泛使用的動(dòng)態(tài)數(shù)組,其擴(kuò)容機(jī)制是保證性能和內(nèi)存使用平衡的關(guān)鍵,默認(rèn)初始容量為10,擴(kuò)容因子為1.5,旨在減少頻繁的內(nèi)存分配和數(shù)據(jù)遷移代價(jià),同時(shí)建議使用預(yù)估計(jì)的初始化容量以減少擴(kuò)容次數(shù)2024-11-11
Java運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Java運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Java之SpringCloudAlibaba Sentinel組件案例講解
這篇文章主要介紹了Java之SpringCloudAlibaba Sentinel組件案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
解析Arthas協(xié)助排查線上skywalking不可用問(wèn)題
這篇文章主要為大家介紹了解析Arthas協(xié)助排查線上skywalking不可用的問(wèn)題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-02-02
JAVA用戶自定義事件監(jiān)聽(tīng)實(shí)例代碼
這篇文章主要介紹了JAVA用戶自定義事件監(jiān)聽(tīng)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04
springboot項(xiàng)目中引入本地依賴jar包并打包到lib文件夾中
這篇文章主要介紹了springboot項(xiàng)目中引入本地依賴jar包,如何打包到lib文件夾中,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
Java Collection 移除元素方法及注意事項(xiàng)
這篇文章主要介紹了Java Collection 移除元素方法及注意事項(xiàng),通過(guò)一個(gè)簡(jiǎn)單實(shí)例給大家講解,需要的朋友可以參考下2020-01-01
websocket實(shí)現(xiàn)方法日志實(shí)時(shí)查詢過(guò)程
本文介紹了一種基于Redis生成唯一key的方案,前端傳遞key至服務(wù)器,服務(wù)器據(jù)此生成日志文件并通過(guò)WebSocket線程實(shí)時(shí)讀取返回內(nèi)容,結(jié)合logback實(shí)現(xiàn)日志記錄與監(jiān)控2025-07-07

