开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情
背景
在网上和书上看到的一个例子:Java 如何实现深拷贝,有哪些方式?当然也是 Java 基础知识及面试题。
浅拷贝 VS 深拷贝
浅拷贝
如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。换句话说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
深拷贝
深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象
深拷贝实现方式
实现 Cloneable 接口
浅拷贝
省略了 get/set 、构造函数、toString 方法。
public class CloneTest implements Cloneable {
int age;
String name;
Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address implements Cloneable {
String prov;
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest cloneTest1 = new CloneTest(10, "nihy", new Address("shanghai"));
CloneTest cloneTest2 = (CloneTest) cloneTest1.clone();
cloneTest2.getAddress().setProv("chongqing");
cloneTest2.setName("chongqing");
cloneTest2.setAge(0);
System.out.println(cloneTest1);
System.out.println(cloneTest2);
}
打印:
CloneTest{age=10, name='nihy', address=Address{prov='chongqing'}}
CloneTest{age=0, name='chongqing', address=Address{prov='chongqing'}}
没有对 clone 方法重写,就是浅拷贝。对输出内容解释:
int age | 基本数据类型,值传递,改变拷贝对原内容无影响 |
---|---|
String name | 不可变对象,在修改的时候,会新创建一个对象,所以对原对象没有影响 |
Address address | 引用类型,指向同一个地址,值改变了,原始值也会改变。 |
深拷贝
需要重写 clone() 接口
@Override
protected Object clone() throws CloneNotSupportedException {
CloneTest cloneTest = (CloneTest) super.clone();
cloneTest.setAddress((Address) address.clone());
return cloneTest;
}
打印:此时对象的引用也会拷贝
CloneTest{age=10, name='nihy', address=Address{prov='shanghai'}}
CloneTest{age=0, name='chongqing', address=Address{prov='chongqing'}}
序列化
上面的实体类不变,只是把实现 Cloneable 接口换成实现 Serializable 接口,并且多实现一个序列化方法:
//<T extends Serializable> :参数 T 必须实现 Serializable
public static <T extends Serializable> T clone(T obj) {
T objClone = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
objClone = (T) ois.readObject();
ois.close();
} catch (Exception e) {
System.out.println(e);
}
return objClone;
}
具体调用的代码:
- 自己实现的代码和 Apache 的 SerializationUtils.clone() 可以说一模一样的。所以直接使用工具包就好
- JSON 解析,比如 FastJSON、jackjson 等
public static void main(String[] args) {
// JSONObject.parseObject();
SerializeTest serializeTest=new SerializeTest(27,"nihy",new AddressTest("shanghai"));
//1. ByteArrayOutputStream、ByteArrayInputStream 实现流的写入写出
// SerializeTest serializeTest1 = clone(serializeTest);
//2. 使用此方式 JSON.parseObject 涉及到的实体类都必须是 public 定义的。否则报错 instance error
SerializeTest serializeTest1 = JSON.parseObject(JSON.toJSONString(serializeTest), SerializeTest.class);
// 3.使用 Apache common 函数 SerializationUtils.clone()
// SerializationUtils.clone() 函数核心逻辑就是 下面我自己写的 clone 方法。可以说是一模一样
// SerializeTest serializeTest1 = SerializationUtils.clone(serializeTest);
serializeTest1.setAge(0);
serializeTest1.setName("nnnn");
serializeTest1.getAddressTest().setProv("chongqing");
System.out.println(serializeTest);
System.out.println(serializeTest1);
}
总结
Java 实现深拷贝的两种方式:
- 实现 Cloneable 接口
在复写 clone() 方法的时候,对里面的对象都要重写,比较麻烦也容易忘记复写,导致出错
比如:cloneTest.setAddress((Address) address.clone());
- 实现 Serializable 接口
比较常用,特别是 fastJson 。Apache 工具包也是经典。