文章目录
浅克隆与深克隆
一.引用赋值
Student stu1 = new Student();
Student stu2 = stu1;
改变stu1或stu2,对方都会跟着改变,因为是引用同个对象。
二、浅克隆/深克隆
浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。
2.1 浅克隆
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
一般步骤:
- 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
- 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址,要变一起变。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
2.2 深克隆
2.2.1 克隆(clone)
如果要用clone()方法实现深克隆,那么对于原型对象的引用类型成员变量,都要实现Clonenable接口。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
2.2.2 序列化(Serialization)
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
具体操作是先使对象实现Serializable接口,然后把对象写到一个流里,再从流里读出来,便可以重建对象。
三、实践使用
3.1 浅克隆
3.1.1 工具类BeanUtils和PropertyUtils进行对象复制
除BeanUtils外还有一个名为PropertyUtils的工具类,它也提供copyProperties()方法,作用与BeanUtils的同名方法十分相似,主要的区别在于BeanUtils提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils使用更普遍一点,犯错的风险更低一点。
BeanUtils.copyProperties(a, b); // BeanUtils.copyProperties("转换前的类", "转换后类");
是浅拷贝
- b中的存在的属性,a中一定要有,但是a中可以有多余的属性;
- a中与b中相同的属性都会被替换,不管是否有值;
- a、 b中的属性要名字相同,才能被赋值,不然的话需要手动赋值;
- Spring的BeanUtils的CopyProperties方法需要对应的属性有getter和setter方法;只是调用对象的set方法,并没有将所有属性拷贝。
- 如果存在属性完全相同的内部类,但是不是同一个内部类,即分别属于各自的内部类,则spring会认为属性不同,不会copy;
- spring和apache的copy属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下。
注意:
其实常见的BeanUtils有2个:
spring有BeanUtils
apache的commons也有BeanUtils。
区别如下:
spring的BeanUtils | commons的BeanUtils | |
---|---|---|
方法 | copyProperty和copyProperties | copyProperties |
参数 | src ,dest | dest,src |
这两个用哪个都行,但是要注意区别。 因为他们2个的src和dest是正好相反的,要特别留意。
3.1.2 指定对象实现克隆
指定对象实现Clonenable接口
3.1.3 数组克隆
Arrays.copyOf()
3.2 深克隆
3.2.1 所有对象都实现克隆
注意:每个对象都要实现Clonenable接口并重写Object类中的clone()方法
3.2.2 序列化
public Outer myclone() {
Outer outer = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
由于是通过字节流序列化实现的深克隆,因此每个对象必须能被序列化,必须实现 Serializable 接口,标识自己可以被序列化,否则会抛出异常 (java.io.NotSerializableException)。
3.2.3 Apache Commons工具包
工具包中的:SerializationUtils.clone(T object);
注意:底层使用的是序列化,因此每个对象都实现Serializable接口即可。通过字节流的方式实现的,只不过这种实现方式是第三方提供了现成的方法,让我们可以直接调用。
3.2.4 通过 JSON 工具类实现深克隆
People p2 = gson.fromJson(gson.toJson(p1), People.class);
使用 JSON 工具类会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆,其他类似的 JSON 工具类实现方式也是一样的。
3.2.5 通过构造方法实现深克隆
// 调用构造函数克隆对象
People p2 = new People(p1.getId(), p1.getName(), new Address(p1.getAddress().getId(), p1.getAddress().getCity()));
如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象。如果字段过多,比较繁琐,不推荐。