1. 数据类型
在 Java 中,数据分为基本数据类型和引用数据类型
- 基本数据类型: 数据直接存储在栈中
- 引用数据类型: 存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里
2. 深拷贝与浅拷贝
深拷贝和浅拷贝是用来描述对象或者对象数组这种引用数据类型的复制场景的
2.1 浅拷贝
- 对于基础数据类型:直接复制数据值;
- 对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变
2.2 深拷贝
- 对于基础数据类型:直接复制数据值;
- 对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象
2.3 总结
在Java里面,无论是深拷贝还是浅拷贝,都需要通过实现Cloneable接口,并实现clone()方法,可以在clone()方法里面实现浅拷贝或者深拷贝的逻辑
实现深拷贝的方法有很多,比如
- 通过序列化的方式实现,也就是把一个对象先序列化一遍,然后再反序列化回来,就会得到一个完整的新对象 (实现 Serializable 接口)
/**
* 深拷贝
*
* 注意:要实现序列化接口
* @return
*/
public Person deepClone() {
try {
// 输出 (序列化)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 输入 (反序列化)
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Person person = (Person) ois.readObject();
return person;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
- 在clone()方法里面重写克隆逻辑,也就是对克隆对象内部的引用变量再进行一次克隆
static class Body implements Cloneable{
public Head head;
public Body(){};
public Body(Head head){
this.head = head;
}
@Override
protected Object clone() throws CloneNotSupportedException{
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
}
static class Head implements Cloneable{
public Face face;
public Head(){}
@Override
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Body body = new Body(new Head(new Face()));
Body body1 = (Body) body.clone();
System.out.println("body == body1 :" + (body == body1));
System.out.println("body.head == body1.head :" + (body.head == body1.head));
}
}
3. 思考
3.1 String也是一个对象且String类并没有实现Cloneable接口,为什么它可以实现深拷贝?
String虽然是一个对象,但对String的修改,在堆上都会创建一个新的对象数组,所以它本身的处理就是深拷贝,不需要做额外处理。
3.2 clone 与 new 对比
3.2.1 对象创建的几种方法
- 使用new关键字
- 使用clone方法
- 反射机制
- 反序列化
1和3都会明确的显式的调用构造函数
2是在内存上对已有对象的克隆 所以不会调用构造函数
4是从文件中还原类的对象 也不会调用构造函数
3.2.2 clone() 和 new 那个更快
利用clone,在内存中进行数据块的拷贝,复制已有的对象,也是生成对象的一种方式
前提是类实现Cloneable接口,Cloneable接口没有任何方法,是一个空接口,也可以称这样的接口为标志接口,只有实现了该接口,才会支持clone操作
Jvm 在复制对象的时候,会检查对象的类是否实现了Cloneable这个接口,如果没有实现,则会报CloneNotSupportedException异常。类似这样的接口还有Serializable接口、RandomAccess接口等。
由于通过复制操作得到对象不需要调用构造函数,只是内存中的数据块的拷贝,但是拷贝对象的效率不是一定会比new的时候的快。
在调用构造方法时,new 只是 clone 的 1/10 的消耗,new 更快
但是如果在构造方法中处理一些逻辑(如字符串截取),那 clone 的速度更快