在日常开发中,会碰到引用拷贝、浅拷贝以及深拷贝,这里面 “引用拷贝”是比较常见、常用的。
一、引用拷贝
如前所说,引用拷贝是相当常用的,比如,当需要在某个方法中遍历一个单向链表,同时有需要返回链表的头结点时,就会使用到引用拷贝。
public ListNode traverse(ListNode head) {
// 创建引用拷贝用于遍历
ListNode current = head;
// 遍历链表
while (current != null) {
// 执行需要的操作
current = current.next;
}
// 返回原始头结点
return head;
}
啊当然,现在应该也很少会用自定义的链表,都是用的容器类,这里呢也只是举个例子。
在上面的代码中,ListNode current = head;就是“引用拷贝”,它的含义是将head对象的引用地址复制之后传递给current对象,此时head、current两个变量都指向同一个ListNdoe实例(内存地址),修改其中一个变量的属性会影响另一个变量。

注意,引用拷贝不会创建新的对象,只是增加了一个指向同一对象的引用。
二、浅拷贝
在 Java 中,浅拷贝(Shallow Copy) 是对象复制的一种方式,它会创建一个新对象,但新对象中的成员变量如果是引用类型,只会复制引用地址(指向原对象的同一块块内存),而不会复制引用指向的实际对象。
简单说:浅拷贝只复制对象本身,不复制对象所引用的其他对象。
2.1浅拷贝的实现方式
通常通过以下两种方式实现浅拷贝:
-
实现Cloneable接口并重写clone()方法 -
通过构造方法手动复制成员变量
2.2浅拷贝示例
假设我们有一个Person类,包含基本类型age和引用类型Address:
// 地址类
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
// 人员类
class Person implements Cloneable {
int age;
Address address;
public Person(int age, Address address) {
this.age = age;
this.address = address;
}
// 重写clone()方法实现浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 调用Object类的clone()方法
}
}
测试浅拷贝的效果:
public class ShallowCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
Person original = new Person(20, addr);
// 浅拷贝得到新对象
Person copy = (Person) original.clone();
// 1. 基本类型:修改拷贝对象不影响原对象
copy.age = 30;
System.out.println(original.age); // 输出:20(原对象不变)
// 2. 引用类型:修改拷贝对象的引用成员,会影响原对象
copy.address.city = "上海";
System.out.println(original.address.city); // 输出:上海(原对象被修改)
}
}
从上面的例子就可以看出,在浅拷贝下,基本类型成员会被直接复制,修改拷贝对象的基本类型成员不影响原对象;而引用类型成员只复制引用地址(新对象和原对象的引用指向同一个实际对象,如下图),因此修改拷贝对象的引用成员会影响原对象。

与引用拷贝不同的是,浅拷贝会创建一个新的对象。但是当该对象的成员包含引用对象时,被拷贝对象和拷贝对象会共用该引用成员对象,是一种没有完全独立的拷贝,如果想要拷贝出一个完全独立,即无论怎么修改拷贝后的对象都不会影响被拷贝对象,需要使用深拷贝。
三、深拷贝
浅拷贝的问题是拷贝后的对象中的引用类型的成员变量和被拷贝对象的引用类型成员变量指向的是同一个内存地址,共用一个对象实例,导致一方修改时另一方会受到影响。
所以,要实现深拷贝就是要在拷贝时不仅复制对象本身,还要递归地复制对象所包含的所有引用类型的成员变量,确保新对象的所有成员都是全新的实例,与原始对象完全隔离。
具体来说,深拷贝需要做到以下几点:
1.对于基本数据类型(如int、float等),直接进行值复制。
2.对于引用类型(如数组、对象等),需要创建新的内存空间,递归地复制原对象中的所有属性和子对象,确保新对象与原对象在内存上是完全独立的。
示例
这里在前面浅拷贝的基础上直接修改clone()函数来实现深拷贝。
// 地址类
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
// 重写Address的clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 人员类
class Person implements Cloneable {
int age;
Address address;
public Person(int age, Address address) {
this.age = age;
this.address = address;
}
// 重写clone()方法实现深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = (Address) address.clone(); // 单独克隆Address对象
return cloned;
}
}
测试效果
public class DeepCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
Person original = new Person(20, addr);
// 深拷贝得到新对象
Person copy = (Person) original.clone();
// 1. 基本类型:修改拷贝对象不影响原对象
copy.age = 30;
System.out.println(original.age); // 输出:20(原对象不变)
// 2. 引用类型:修改拷贝对象的引用成员,不影响原对象
copy.address.city = "上海";
System.out.println(original.address.city); // 输出:北京(原对象不变)
}
}
此时original对象和copy对象就是完全独立的两个对象了,对象的属性均指向不同的内存地址,修改任意一方都不会影响到另一方,这就是深拷贝。

当然,深拷贝的实现方法是比较多样的,可以向上面那样手动实现Cloneable接口并重写clone方法,也可以使用序列化/反序列化方法,以及使用第三方库(如Apache Commons Lang中的SerializationUtils)等,这里就不一一展示了。
需要注意的是,深拷贝可能会带来一定的性能开销,特别是在对象结构复杂或层级较深时,因此在设计时需要权衡是否需要使用深拷贝。
----------------------------------------------------(这是一条分割线)------------------------------------------------------
其实,在日常开发的基础业务场景(如简单数据传递、CRUD 接口)中,浅拷贝、深拷贝是不常见的,但 “引用拷贝”却几乎无处不在.
这是因为大部分场景不需要 “完全隔离对象”,比如从数据库查数据(User user = userMapper.getById(1);)、接口返回数据(return ResponseEntity.ok(user);)—— 我们只需要读取user的属性(如user.getName()),不需要修改它的内容;当需要 “修改副本却不影响原始对象” 且对象有嵌套时,浅拷贝 / 深拷贝的区别就会立刻凸显,不处理就会出 bug。


6428

被折叠的 条评论
为什么被折叠?



