一、Shadow Clone
浅克隆
是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:
二、Deep Clone
深克隆
是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
三、Java 实现克隆
需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下:
/**
* author:wy
* describe:Java 实现克隆
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People implements Cloneable {
public static void main(String[] args) {
People p1 = new People();
p1.setId(1);
p1.setName("Java");
People p2 = null;
try {
// 克隆p1对象
p2 = (People) p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(p1);// People(id=1, name=Java)
System.out.println(p2);// People(id=1, name=Java)
System.out.println(p1 == p2);// false
System.out.println(p1.getClass() == p2.getClass());// true
System.out.println(p1.equals(p2));// true
p1.setName("Android");
System.out.println(p1);// People(id=1, name=Android)
System.out.println(p2);// People(id=1, name=Java)
}
// 属性
private Integer id;
private String name;
/**
* 重写 Object 类中的 clone() 方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
四、知识扩展
1. clone()
深克隆
// 是使用 native 修饰的本地方法,因此执行的性能会很高
protected native Object clone() throws CloneNotSupportedException;
从以上源码的注释信息中我们可以看出,Object 对 clone() 方法的约定有三条:
- 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
- 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
- Ba对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。
2. Arrays.copyOf()
浅克隆
@Test
public void test() {
People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);
// 修改原型对象的第一个元素的值
o1[0].setName("Android");
System.out.println(o1[0]);// People(id=1, name=Android)
System.out.println(o2[0]);// People(id=1, name=Android)
}
因为数组比较特殊,数组本身就是引用类型,因此在使用
Arrays.copyOf()
其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。
3. 深克隆实现方式汇总
深克隆的实现方式有很多种,大体可以分为以下几类:
- 所有对象都实现克隆方法;
- 通过构造方法实现深克隆;
- 使用 JDK 自带的字节流实现深克隆;
- 使用第三方工具实现深克隆,比如 Apache Commons Lang;
- 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。
3.1 所有对象都实现克隆
/**
* author:wy
* describe:收货人
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Consignee implements Cloneable {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address(101, "上海");
Consignee c1 = new Consignee(1, "骑士", address);
// 克隆 c1 对象
Consignee c2 = c1.clone();
// 修改原型对象
c1.getAddress().setCity("北京");
System.out.println(c1);// Consignee(id=1, name=骑士, address=Address(id=101, city=北京))
System.out.println(c2);// Consignee(id=1, name=骑士, address=Address(id=101, city=上海))
}
private Integer id;
private String name;
private Address address;// 包含 Address 对象
@Override
protected Consignee clone() throws CloneNotSupportedException {
Consignee consignee = (Consignee) super.clone();
consignee.setAddress(this.address.clone());// 引用类型克隆赋值
return consignee;
}
}
/**
* author:wy
* describe:地址
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address implements Cloneable {
private Integer id;
private String city;
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。
3.2 通过构造方法实现深克隆
《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象,实现代码如下:
/**
* author:wy
* describe:收货人2
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Consignee2 {
public static void main(String[] args) {
Address2 address = new Address2(102, "北京");
Consignee2 c1 = new Consignee2(2, "骑士梦", address);
// 调用构造函数克隆对象
Consignee2 c2 = new Consignee2(c1.getId(), c1.getName(),
new Address2(c1.getAddress().getId(), c1.getAddress().getCity()));
// 修改原型对象
c1.getAddress().setCity("上海");
System.out.println(c1);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=上海))
System.out.println(c2);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=北京))
}
private Integer id;
private String name;
private Address2 address;// 包含 Address 对象
}
/**
* author:wy
* describe:地址2
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address2 {
private Integer id;
private String city;
}
从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。