浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:
深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
在 Java 语言中要实现克隆则需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法。
package com.hkj.springboot.cloneTest;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author liujy
* @description Java中的克隆
* @since 2020-12-11 11:23
*/
public class ArraysCopyOfMethodTest {
public static void main(String[] args) throws CloneNotSupportedException {
CloneObj cloneObj = new CloneObj();
cloneObj.setName("cc");
CloneObj backup = (CloneObj) cloneObj.clone();
System.out.println(backup.getName());
}
@Getter
@Setter
@NoArgsConstructor
static class CloneObj implements Cloneable {
private String name;
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public CloneObj(String name) {
this.name = name;
}
}
}
如果是数组类型,我们可以直接使用 Arrays.copyOf() 来实现克隆,那么 Arrays.copyOf() 是深克隆还是浅克隆呢?
验证代码如下:
package com.hkj.springboot.cloneTest;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Arrays;
/**
* @author liujy
* @description 验证Arrays.valueOf是深克隆还是浅克隆
* @since 2020-12-11 11:23
*/
public class ArraysCopyOfMethodTest {
public static void main(String[] args) {
// 测试Arrays.valueOf是深克隆还是浅克隆
Object[] obj1 = {new CloneObj("zhangsan"), "str"};
Object[] obj2 = Arrays.copyOf(obj1, obj1.length);
CloneObj c = (CloneObj) obj1[0];
c.setName("lisi");
obj1[1] = "string";
CloneObj cc1 = (CloneObj) obj1[0];
CloneObj cc2 = (CloneObj) obj2[0];
System.out.println(cc1.getName() + "--" + obj1[1]);
System.out.println(cc2.getName() + "--" + obj2[1]);
}
@Getter
@Setter
@NoArgsConstructor
static class CloneObj implements Cloneable {
private String name;
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public CloneObj(String name) {
this.name = name;
}
}
}
由运行结果可知,Arrays.valueOf是浅克隆。因为数组比较特殊数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。
深克隆实现方式汇总
深克隆的实现方式有很多种,大体可以分为以下几类:
1.所有对象都实现克隆方法;
2.通过构造方法实现深克隆;
3.使用 JDK 自带的字节流实现深克隆;
4.使用第三方工具实现深克隆,比如 Apache Commons Lang;
5.使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。
所有对象都实现克隆方法
package com.hkj.springboot.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* @author liujy
* @description 深克隆
* @since 2020-12-11 14:22
*/
public class DeepCloneTest1 {
@Getter
@Setter
static class CloneObj implements Cloneable {
private String name;
private CloneObjProp cloneObjProp;
protected CloneObj clone() throws CloneNotSupportedException {
CloneObj cloneObj = (CloneObj) super.clone();
cloneObj.setCloneObjProp(this.cloneObjProp.clone());
return cloneObj;
}
public CloneObj(String name) {
this.name = name;
}
}
@Getter
@Setter
static class CloneObjProp implements Cloneable {
private String desc;
protected CloneObjProp clone() throws CloneNotSupportedException {
CloneObjProp cloneObjProp = (CloneObjProp) super.clone();
return cloneObjProp;
}
public CloneObjProp(String desc) {
this.desc = desc;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 深拷贝实现-所有对象都实现Cloneable接口
CloneObj cloneObj = new CloneObj("cc");
CloneObjProp cloneObjProp = new CloneObjProp("this is cc");
cloneObj.setCloneObjProp(cloneObjProp);
// 进行克隆
CloneObj cloneObjBackup = cloneObj.clone();
// 修改原型对象
cloneObj.setName("dd");
cloneObj.getCloneObjProp().setDesc("this is dd");
System.out.println(cloneObj.getName() + "--" + cloneObj.getCloneObjProp().getDesc());
System.out.println(cloneObjBackup.getName() + "--" + cloneObjBackup.getCloneObjProp().getDesc());
}
}
通过构造方法实现深克隆
package com.hkj.springboot.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* @author liujy
* @description 深克隆
* @since 2020-12-11 14:37
*/
public class DeepCloneTest2 {
@Getter
@Setter
static class CloneObj {
private String name;
private CloneObjProp cloneObjProp;
public CloneObj(String name) {
this.name = name;
}
public CloneObj(String name, CloneObjProp cloneObjProp) {
this.name = name;
this.cloneObjProp = cloneObjProp;
}
}
@Getter
@Setter
static class CloneObjProp {
private String desc;
public CloneObjProp(String desc) {
this.desc = desc;
}
}
public static void main(String[] args) {
// 深拷贝实现-通过构造方法
CloneObj cloneObj = new CloneObj("cc");
CloneObjProp cloneObjProp = new CloneObjProp("this is cc");
cloneObj.setCloneObjProp(cloneObjProp);
// 通过构造方法进行克隆
CloneObj cloneObjBackup = new CloneObj(cloneObj.getName(), new CloneObjProp(cloneObj.getCloneObjProp().getDesc()));
// 修改原型对象
cloneObj.setName("dd");
cloneObj.getCloneObjProp().setDesc("this is dd");
System.out.println(cloneObj.getName() + "--" + cloneObj.getCloneObjProp().getDesc());
System.out.println(cloneObjBackup.getName() + "--" + cloneObjBackup.getCloneObjProp().getDesc());
}
}
克隆设计理念猜想
对于克隆为什么要这样设计,官方没有直接给出答案,我们只能凭借一些经验和源码文档来试着回答一下这个问题。Java 中实现克隆需要两个主要的步骤,一是 实现 Cloneable 空接口,二是重写 Object 的 clone() 方法再调用父类的克隆方法 (super.clone()),那为什么要这么做?
从源码中可以看出 Cloneable 接口诞生的比较早,JDK 1.0 就已经存在了,因此从那个时候就已经有克隆方法了,那我们怎么来标识一个类级别对象拥有克隆方法呢?克隆虽然重要,但我们不能给每个类都默认加上克隆,这显然是不合适的,那我们能使用的手段就只有这几个了:
在类上新增标识,此标识用于声明某个类拥有克隆的功能,像 final 关键字一样;
使用 Java 中的注解;
实现某个接口;
继承某个类。
先说第一个,为了一个重要但不常用的克隆功能, 单独新增一个类标识,这显然不合适;再说第二个,因为克隆功能出现的比较早,那时候还没有注解功能,因此也不能使用;第三点基本满足我们的需求,第四点和第一点比较类似,为了一个克隆功能需要牺牲一个基类,并且 Java 只能单继承,因此这个方案也不合适。采用排除法,无疑使用实现接口的方式是那时最合理的方案了,而且在 Java 语言中一个类可以实现多个接口。
那为什么要在 Object 中添加一个 clone() 方法呢?
因为 clone() 方法语义的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了。