Java 中使用 Gson 实现深度克隆 #什么是深克隆与浅克隆?#clone方法为什么不能直接通过某个对象实例在外部类调用?
🌐Gson的jar包提供到本文上方,欢迎自取🌐
前言
🌐在 Java 编程中,克隆对象是一项常见的需求,特别是在处理不可变对象、避免引用传递时,深度克隆显得尤为重要。虽然 Java 提供了 clone()
方法,但由于它的限制(如 Cloneable
接口的复杂性),实际项目中并不常用。为此,许多开发者选择使用第三方工具来实现深度克隆,其中,Gson 是一个非常流行且简洁的解决方案。
本文将介绍什么是深度克隆与浅克隆、clone()方法调用的细节问题和如何通过 Gson 实现对象的深度克隆,并分析其优缺点。
什么是深度克隆?浅克隆?
对象克隆指的是把A对象的属性值完全拷贝给B对象,也叫对象拷贝/对象复制
但是object默认的是浅克隆
不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来
基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值。
🔴注意:浅克隆会导致引用数据类型的地址多个对象共用的情况,如果一个对象对其地址所记录的值进行了修改,另外一个对象里面的的值也会跟着一起变动,因为两个对象的地址值相同,指向相同的具体数值。
🌐clone()的问题与方法实现——以User类为例
❓问题:
在User类中重写clone()方法,不能直接在main下创建对象直接调用,至于为什么不能这样调用在寻找资料和ai后都得不到很好地理解,把原因归结与protected修饰符不能直接通过某个对象实例在外部类调用,如下图:
直接调用会出现'clone()' has protected access in 'java.lang.Object'报错
但是我换了别的方法试了一下别的方法来测试得到不一样的结果
我先在Animal类中定义了一个protected修饰的eat方法
然后在另外一个包创建一个Dog类,继承Animal,而且不对eat方法作重写
最后我们到测试类的main方法下创建一个Dog对象,并调用继承父类用protected修饰的eat()方法
最后运行发现,没有任何报错而且成功调用了继承父类用protected修饰的eat()方法
得到运行结果:
🔷思考与结论:
同样是父类其他包的子类,同样是父类用protected修饰的方法,为什么不能在main方法中被子类对象实例调用呢?
为了解决这个疑惑,我找到了clone()的源代码,发现有一个native关键字,可能是native的原因导致上述问题,如下图:
而且通过学习我们得知,想要实现JavaBean类调用clone()方法还需要实现一个
Cloneable接口。
Cloneable
是一个标记接口(没有方法),它告诉 JVM 某个类是可以被克隆的经过上述分析,个人没有找到protected的影响,但是找到了两个可能的原因就是:
- 实现克隆调用需要实现
Cloneable接口
- 受到native关键字的影响
🔷实现:
- 在JavaBean类中重写Object中的clone方法
- 让JavaBean类实现Cloneable接口
- 创建对象并调用clone方法(clone返回的是Object对象,需要强制转换)
1.在JavaBean类中重写Object中的clone方法:
2.让JavaBean类实现Cloneable接口:
3.创建对象并调用clone方法(注意接收对象的时候要强转):
先点击向方法签名添加异常
这样,一个浅克隆就实现了
但是我们上面提及,浅克隆会导致引用数据类型的地址多个对象共用的情况,如果一个对象对其地址所记录的值进行了修改
代码示例:
public static void main(String[] args) throws CloneNotSupportedException {//定义一个数组并赋值int[] arr = {1,2,3,4,5};//新建一个User对象User u1 = new User(001,"qiao","123456","kimi",arr);User u2 = new User();u2 = (User) u1.clone();System.out.println(u1);System.out.println(u2);int[] data = u1.getData();data[0] = 100;System.out.println(u1);//User{id=1, username='qiao', password='123456', path='kimi', data=[100, 2, 3, 4, 5]}System.out.println(u2);//User{id=1, username='qiao', password='123456', path='kimi', data=[100, 2, 3, 4, 5]}}
而深度克隆指的是不仅复制对象本身,还复制该对象所有引用的对象,确保克隆对象与原对象之间没有共享的引用。与之相对的浅克隆,只会复制对象本身,而不会递归复制其引用的对象。
🌐通过clone()方法实现深克隆
我们需要修改一下JavaBean类中的clone()方法重写内容:
@Overrideprotected Object clone() throws CloneNotSupportedException {int[] data = this.data;int[] newData = new int[data.length];for (int i = 0; i < data.length; i++) {newData[i] = data[i];}User u = (User)super.clone();u.data = newData;return u;}
但是这样操作有点麻烦,所以我们会使用一个第三方工具Gson
Gson 简介
Gson 是 Google 提供的一个用于将 Java 对象与 JSON 互相转换的库。它具有以下特点:
-
支持简单和复杂的对象转换。
-
无需配置即可处理大部分对象。
-
能够处理泛型类。
-
性能高效、使用简单。
尽管 Gson 主要用于 JSON 序列化与反序列化,但它的这一特性可以巧妙地用于对象的深度克隆。通过将对象转换为 JSON 字符串,再从 JSON 字符串中反序列化回对象,我们可以实现深度克隆。
🌐使用 Gson 实现深度克隆
使用之前先在项目目录下创建lib包,将gson的jar包放在lib包下:
然后右键该jar包,点击黄色箭头处完成导入:
接下来实现对象的深度克隆非常简单,只需以下两步:
-
将对象序列化为 JSON 字符串(toJson)。
-
从 JSON 字符串反序列化为一个新的对象(fromJson)。
示例代码
import com.google.gson.Gson;public class Main {public static void main(String[] args) throws CloneNotSupportedException {int[] arr = {1,2,3,4,5};User u1 = new User(001,"zhangsan","123456","gril",arr);System.out.println(u1);//第三方深克隆工具Gson gson = new Gson();String json = gson.toJson(u1);User u2 = gson.fromJson(json, User.class);System.out.println(u1);//User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}System.out.println(u2);//User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}int[] data = u1.getData();data[0] = 100;System.out.println(u1);//User{id=1, username='zhangsan', password='123456', path='gril', data=[100, 2, 3, 4, 5]}System.out.println(u2);//User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}
}
运行结果
原对象:User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}
克隆对象:User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}
修改后的原对象:User{id=1, username='zhangsan', password='123456', path='gril', data=[100, 2, 3, 4, 5]}
克隆对象不受影响:User{id=1, username='zhangsan', password='123456', path='gril', data=[1, 2, 3, 4, 5]}
通过输出结果可以看出,原对象和克隆对象之间没有共享引用,修改克隆对象的地址不会影响原对象,说明我们成功实现了深度克隆。
Gson 深度克隆的优缺点
🌐优点
实现简单:使用 Gson 实现深度克隆的代码非常简洁,仅需两步即可完成。
适用于复杂对象:Gson 可以处理包含嵌套对象和集合的复杂 Java 对象。
无需额外实现接口:不同于
clone()
方法,使用 Gson 不需要让类实现Cloneable
接口,也不需要自己处理克隆逻辑。
🌐缺点
性能开销:由于 Gson 需要将对象转换为 JSON 字符串,再从 JSON 字符串中解析出对象,这个过程相较于原生的克隆操作稍慢,特别是在处理大对象时性能损耗较为明显。
不适用于所有场景:Gson 无法处理带有瞬态字段(
transient
)或不希望被序列化的对象,这些字段会在克隆过程中丢失。依赖 JSON 表示:对象的所有字段都必须能够被正确转换为 JSON 格式,对于某些复杂类型或带有循环引用的对象,Gson 可能无法处理。
总结
在 Java 中,深度克隆是一个常见需求,而 Gson 提供了一种简单高效的解决方案。虽然它的主要用途是序列化与反序列化,但它也能够通过这种方式轻松实现深度克隆。对于大部分场景,Gson 是一个非常不错的选择。不过,如果你的对象过于复杂或者对性能要求较高,可能需要考虑其他克隆工具。