浅拷贝只是拷贝对象的引用地址,两个引用地址指向的对象还是同一个,修改其中一个,另一个也会随之改变,因为这个引用地址指向的对象压根是同一个
而深拷贝是将对象和值都拷贝过来,形成一个新的对象,两者之间是独立的两个对象。
在Java中,深拷贝又有两种实现形式:深克隆和浅克隆。
clone方法是Object类中的一个被protected和native修饰的方法,被native就代表它的实现源码是用c++实现的,只不过是我们无法去修改它的代码罢了。
为子类提供可以重写的clone()方法,目的是实现对象的浅克隆和深克隆
在堆内存中新开辟一段空间,然后把被克隆对象的属性和方法赋值一份到新开辟的空间里面(副本)。
对于基本数据类型而言,是复制其的副本到新开辟的空间里面
对于引用数据类型而言,只是复制了引用的地址,并没有开辟新的空间,新的空间里面的引用和被克隆的里面的引用都指向于同一个空间
public class Hello implements Cloneable{
String str="hello";
public Object clone() {
Object cloneObject=null;
try {
cloneObject=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneObject;
}
浅克隆存在一个问题:只能克隆对象主体,不能克隆引用着的属性对象,两个对象的关联引用对象仍然是同一个,解决办法是将属性对象的类也实现Cloneable接口,重写clone()方法,当引用对象过多或出现嵌套引用时,这种方法特别操蛋。所以,《java核心技术卷》中不推荐这种克隆。
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
对于基本数据类型而言,是复制其的副本到新开辟的空间里面
对于引用数据类型而言,并不是复制了引用的地址,而是开辟了一个新的引用对象的空间,并把引用地址里面的属性和方法拷贝一份到新的引用对象的空间中,此时克隆对象里面的引用就指向于这个新的空间
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static T clone(T obj)
throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream
//或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,
//这一点不同于对外部资源(如文件流)的释放
}
}
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33,
new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 因为在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}