字符串和对象的深拷贝和浅拷贝
- 【一】基本介绍
- 【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)生成新对象(深拷贝)。
若以上均不满足,通过反射拷贝对象的所有字段(包括私有字段),递归处理引用类型字段(深拷贝)。