字符串和對象的深拷貝和淺拷貝
- 【一】基本介紹
- 【1】淺拷貝
- 【2】深拷貝
- 【二】字符串的拷貝
- 【1】字符串的 “淺拷貝”
- 【2】字符串的 “深拷貝”
- 【三】對象的拷貝
- 【1】淺拷貝(Shallow Copy)
- 【2】深拷貝(Deep Copy)
- 【四】字符串和對象拷貝的核心區別
- 【五】介紹ObjectUtil的clone方法
- 【1】ObjectUtil.clone的核心功能
- 【2】使用案例
- (1)基本類型與字符串拷貝
- (2)數組拷貝(深拷貝)
- (3)自定義對象拷貝
- 【3】實現原理(Hutool為例)
【一】基本介紹
【1】淺拷貝
僅拷貝數據的 “表層”。對于基本數據類型(如int、float),直接拷貝值;對于引用類型(如對象、數組),僅拷貝引用地址(原對象和拷貝對象共享同一個引用對象)。
【2】深拷貝
拷貝數據的 “全部層級”。不僅拷貝基本類型的值,還會遞歸拷貝所有引用類型所指向的對象,原對象和拷貝對象完全獨立,互不影響。
【二】字符串的拷貝
字符串(String)是特殊的引用類型,其拷貝行為受不可變性影響(String類被final修飾,一旦創建不可修改)。
【1】字符串的 “淺拷貝”
本質是引用傳遞,由于字符串不可變,直接賦值或使用clone()(字符串的clone()方法返回自身)時,拷貝的是引用,但因不可變性,不會出現共享修改的問題。
String original = "hello";
String copy = original; // 拷貝引用(淺拷貝)// 字符串不可變:修改copy會創建新對象,不影響original
copy = "world";
System.out.println(original); // 輸出:hello(原對象未變)
特點:原字符串和拷貝字符串的引用指向同一對象,但因不可修改,看似 “安全”,實際仍是淺拷貝(共享引用)。
【2】字符串的 “深拷貝”
無需額外操作
由于字符串不可變,任何 “修改” 都會創建新對象,因此即使是淺拷貝,也能達到類似深拷貝的效果。若需顯式創建新字符串對象,可通過以下方式:
String original = "hello";
// 方式1:使用構造方法創建新對象(深拷貝效果)
String deepCopy1 = new String(original);
// 方式2:使用intern()(但可能復用常量池對象,視情況而定)
String deepCopy2 = original.intern(); // 修改拷貝對象不會影響原對象(因不可變,實際是創建新對象)
deepCopy1 = deepCopy1 + "world";
System.out.println(original); // 輸出:hello(原對象未變)
特點:字符串的不可變性使得 “淺拷貝” 和 “深拷貝” 的區別被弱化 —— 無論哪種方式,修改拷貝都不會影響原對象,因此通常無需專門實現深拷貝。
【三】對象的拷貝
對象(如自定義類實例)是典型的引用類型,其拷貝需明確區分淺拷貝和深拷貝,否則可能因共享引用導致意外問題。
【1】淺拷貝(Shallow Copy)
僅拷貝對象本身和基本類型字段,引用類型字段僅拷貝引用(原對象和拷貝對象共享引用對象)。
(1)實現方式:
重寫Object類的clone()方法(需實現Cloneable接口)。
默認的clone()方法為淺拷貝。
(2)示例
class Address { // 引用類型(地址類)String city;public Address(String city) { this.city = city; }
}class Person implements Cloneable { // 實現Cloneable接口String name; // 基本類型(String是特殊引用類型,但不可變)int age; // 基本類型Address address; // 引用類型public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}// 重寫clone()實現淺拷貝@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // 默認淺拷貝}
}// 測試淺拷貝
public class ShallowCopyDemo {public static void main(String[] args) throws CloneNotSupportedException {Address addr = new Address("Beijing");Person original = new Person("Alice", 20, addr);// 淺拷貝Person copy = (Person) original.clone();// 1. 基本類型和不可變引用類型(String):修改拷貝不影響原對象copy.name = "Bob";copy.age = 21;System.out.println(original.name); // 輸出:Alice(原對象name未變)System.out.println(original.age); // 輸出:20(原對象age未變)// 2. 引用類型(Address):修改拷貝的引用對象,原對象受影響copy.address.city = "Shanghai";System.out.println(original.address.city); // 輸出:Shanghai(原對象address被修改)}
}
結論:淺拷貝中,引用類型字段(如Address)的修改會同時影響原對象和拷貝對象(因共享引用)。
【2】深拷貝(Deep Copy)
完全拷貝對象及其所有引用類型字段指向的對象,原對象和拷貝對象完全獨立。
(1)實現方式:
方式 1:重寫clone()方法時,對引用類型字段也進行clone()(遞歸淺拷貝)。
方式 2:通過序列化(Serializable)將對象轉為字節流,再反序列化為新對象。
方式 3:使用 JSON 工具(如 Jackson、Gson)將對象轉為 JSON 字符串,再解析為新對象。
(2)示例一:遞歸 clone 實現深拷貝
class Address implements Cloneable { // 引用類型實現CloneableString city;public Address(String city) { this.city = city; }@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // Address的淺拷貝}
}class Person implements Cloneable {String name;int age;Address address;public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}// 重寫clone()實現深拷貝:對引用類型字段也進行clone@Overrideprotected Object clone() throws CloneNotSupportedException {Person copy = (Person) super.clone(); // 先淺拷貝Person本身copy.address = (Address) this.address.clone(); // 再拷貝引用類型Addressreturn copy;}
}// 測試深拷貝
public class DeepCopyDemo {public static void main(String[] args) throws CloneNotSupportedException {Address addr = new Address("Beijing");Person original = new Person("Alice", 20, addr);// 深拷貝Person copy = (Person) original.clone();// 修改拷貝的引用類型字段copy.address.city = "Shanghai";System.out.println(original.address.city); // 輸出:Beijing(原對象不受影響)}
}
(3)示例二:序列化實現深拷貝
import java.io.*;class Address implements Serializable { // 需實現SerializableString city;public Address(String city) { this.city = city; }
}class Person implements Serializable { // 需實現SerializableString name;int age;Address address;// 深拷貝方法:序列化+反序列化public Person deepCopy() throws IOException, ClassNotFoundException {// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);// 反序列化ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Person) ois.readObject();}
}// 測試
public class SerializationDemo {public static void main(String[] args) throws Exception {Person original = new Person("Alice", 20, new Address("Beijing"));Person copy = original.deepCopy();copy.address.city = "Shanghai";System.out.println(original.address.city); // 輸出:Beijing(完全獨立)}
}
結論:深拷貝中,原對象和拷貝對象的所有字段(包括引用類型)完全獨立,修改互不影響。
【四】字符串和對象拷貝的核心區別
(1)字符串(String)
拷貝引用,因不可變性,修改時會創建新對象,原對象不受影響。與淺拷貝效果一致(因不可變性,無需遞歸拷貝) 不可變性導致 “淺拷貝即安全”,無需專門深拷貝
(2)對象(引用類型)
引用類型字段共享引用,修改會相互影響。完全拷貝所有層級,修改互不影響 需顯式實現深拷貝(如遞歸clone、序列化)
(3)適用場景
1-淺拷貝:適用于對象中僅包含基本類型或不可變引用類型(如String),或需共享引用對象的場景(如多對象共享配置信息)。
2-深拷貝:適用于對象包含可變引用類型(如自定義類、數組),且需完全隔離原對象和拷貝對象的場景(如多線程操作、數據備份)。
【五】介紹ObjectUtil的clone方法
在 Java 工具類中,ObjectUtil通常是一個自定義或第三方工具類(如 Hutool、Apache Commons 等),用于簡化對象操作,其中clone方法用于實現對象的拷貝。不同工具類的clone方法實現可能略有差異,但核心功能都是封裝對象克隆的復雜邏輯,提供更簡潔的 API。以下以Hutool 工具類庫的ObjectUtil.clone方法為例,詳細介紹其功能、用法和特性
【1】ObjectUtil.clone的核心功能
ObjectUtil.clone方法的主要作用是快速實現對象的深拷貝或淺拷貝,無需手動處理Cloneable接口、序列化等底層細節,簡化對象拷貝的代碼實現。
Hutool 的ObjectUtil中,clone方法通過判斷對象類型,自動選擇最優的拷貝方式:
(1)對于基本類型、字符串(String)等不可變類型,直接返回原值(因不可變特性,無需拷貝)。
(2)對于數組,遞歸拷貝數組元素(深拷貝)。
(3)對于實現Cloneable接口的對象,調用其clone()方法(可能是淺拷貝,取決于對象自身實現)。
(4)對于實現Serializable接口的對象,通過序列化 + 反序列化實現深拷貝。
(5)對于普通對象,嘗試通過反射拷貝字段(深拷貝)。
【2】使用案例
(1)基本類型與字符串拷貝
import cn.hutool.core.util.ObjectUtil;public class CloneDemo {public static void main(String[] args) {// 字符串拷貝(因不可變,直接返回引用,但不影響安全性)String str = "hello";String strClone = ObjectUtil.clone(str);System.out.println(str == strClone); // true(共享引用,因不可變安全)// 基本類型包裝類拷貝Integer num = 123;Integer numClone = ObjectUtil.clone(num);System.out.println(num == numClone); // true(Integer常量池復用)}
}
(2)數組拷貝(深拷貝)
import cn.hutool.core.util.ObjectUtil;public class ArrayCloneDemo {public static void main(String[] args) {int[] arr = {1, 2, 3};int[] arrClone = ObjectUtil.clone(arr);arrClone[0] = 100;System.out.println(arr[0]); // 1(原數組不受影響,深拷貝)}
}
(3)自定義對象拷貝
假設存在自定義類User(實現Serializable接口以支持深拷貝)
import java.io.Serializable;class User implements Serializable {private String name;private int age;private Address address; // 引用類型字段// 構造方法、getter、setter省略
}class Address implements Serializable {private String city;// 構造方法、getter、setter省略
}
使用ObjectUtil.clone實現深拷貝:
import cn.hutool.core.util.ObjectUtil;public class ObjectCloneDemo {public static void main(String[] args) {Address addr = new Address("Beijing");User user = new User("Alice", 20, addr);// 深拷貝User userClone = ObjectUtil.clone(user);// 修改拷貝對象的引用字段userClone.getAddress().setCity("Shanghai");// 原對象不受影響(深拷貝成功)System.out.println(user.getAddress().getCity()); // Beijing}
}
【3】實現原理(Hutool為例)
ObjectUtil.clone的底層邏輯大致分為以下步驟:
(1)判斷對象類型:
若為null,直接返回null。
若為基本類型或字符串,返回原值(利用不可變性)。
若為數組,遞歸拷貝每個元素(數組的深拷貝)。
(2)嘗試克隆方式:
若對象實現Cloneable接口,調用其clone()方法(注意:若對象自身clone()是淺拷貝,結果仍為淺拷貝)。
若對象實現Serializable接口,通過序列化(ObjectOutputStream)和反序列化(ObjectInputStream)生成新對象(深拷貝)。
若以上均不滿足,通過反射拷貝對象的所有字段(包括私有字段),遞歸處理引用類型字段(深拷貝)。