简介:深克隆和浅克隆的区别,实现深克隆的三种方式。
深克隆和浅克隆
- 浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
- 深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象
public static void main(String[] args) throws CloneNotSupportedException {
// 创建被赋值对象
People p1 = new People();
p1.setId(1);
p1.setName("Java");
// 克隆 p1 对象
People p2 = (People) p1.clone();
System.out.println("p2:" + p2.getName());
}
static class People implements Cloneable{}
p2:Java
实现克隆则需要实现Cloneable接口,并重写Object类的clone()方法
Arrays.copyOf()
People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);
o1[0].setName("Jdk");
System.out.println("o1:" + o1[0].getName());
System.out.println("o2:" + o2[0].getName());
o1:Jdk
o2:Jdk
- 在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆,因为数组本身就是引用类型,在使用copyOf()时知识复制了引用地址给克隆对象。
深克隆实现
/**
* 用户类
*/
public class People {
private Integer id;
private String name;
private Address address; // 包含 Address 引用对象
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
public class Address {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
所有对象都实现克隆
//使People和Address类都实现Cloneable接口,从而实现People的深克隆
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建被赋值对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 克隆 p1 对象
People p2 = p1.clone();
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
static class People implements Cloneable{}
static class Address implements Cloneable{}
p1:西安 p2:北京
字节流
public class ThirdExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 通过字节流实现克隆
People p2 = (People) StreamClone.clone(p1);
}
/**
* 通过字节流实现克隆
*/
static class StreamClone {
public static <T extends Serializable> T clone(People obj) {
T cloneObj = null;
try {
// 新建字节数组输出流缓冲区
ByteArrayOutputStream bo = new ByteArrayOutputStream();
//生成序列化的关键类对象ObjectOutputStream
//传入一个OutputStream参数表示将对象二进制流写入指定的OutputStream,这里即字节数组
ObjectOutputStream oos = new ObjectOutputStream(bo);
//将对象转换成二进制流
oos.writeObject(obj);
oos.close();
// 分配内存,写入原始对象,生成新对象
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
ObjectInputStream oi = new ObjectInputStream(bi);
// 返回生成的新对象
cloneObj = (T) oi.readObject();
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
- 先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆。
- 每个对象必须能被序列化,必须实现Serializable接口,否则会抛出异常java.io.NotSerializableException
JSON工具类
import com.google.gson.Gson;
public class FifthExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 调用 Gson 克隆对象
Gson gson = new Gson();
People p2 = gson.fromJson(gson.toJson(p1), People.class);
}
使用Gson会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆。