拷贝就是将一个对象的全部属性copy到另一个同类对象中。即使得某个对象以一个同类对象为原型,通过拷贝其全部属性来实现对象的拷贝。
同一个对象
注意!只要是拷贝,就一定是两个不同的对象。两个相同的对象引用相同,表示如下:
这不是拷贝,只是对同一个对象引用起了不同的变量名,绝不可将同一个对象视为拷贝。
浅拷贝
简述
浅拷贝就是只拷贝了对象的内容值,这个内容值包括基本类型值和引用值,表示如下:
表现在外,就是基本类型被重新拷贝了一份,而引用类型则只拷贝了引用值,但这种理解太过浅薄。就应该理解了Java的值和引用以后,将浅拷贝理解成不管什么属性全部都拷贝了值。
可以注意下String
的内容保存在常量池中,所以对其做修改只是改变了成员变量所引用的常量池的串,不会影响其它拷贝对象中的该成员。
实现浅拷贝
Object
类的clone()
方法即是实现了浅拷贝,另外也可以自己加个拷贝构造函数来实现浅拷贝。
一般只要继承Cloneable
接口,在实现的clone()
方法中用super
关键字调用Object
类的clone()
方法,返回一个Object
实例,然后强制转换成本类型并返回,注意处理异常即可。
package org.lzh;
//汉堡的面包片
public class Bread implements Cloneable{
private int facId;//面包片厂商id
private String name;//面包片品牌名
public Bread(){}
//拷贝构造
public Bread(Bread bread){
this.facId=bread.facId;
this.name=bread.name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这里为了方便就不加getter、setter和有参构造了。
深拷贝
深拷贝,就是对所有的属性都独立地复制一份。对于基本类型属性即是复制一份值(成员变量自会提供独立的空间),而对于引用类型的属性就是重新建立了一个一模一样的对象,引用值不再相同(不再指向同一个对象)。表示如下:
使用clone方法实现深拷贝
看一下要拷贝的对象的属性,对于基本类型和String
类型,无需特殊处理,因为他们的修改都不会在拷贝对象之间产生影响。
对于其它的引用类型,则需要在调用了Object
的clone()
实现浅拷贝后再调用下它们的clone()
方法得到拷贝的属性对象,传给拷贝对象。
package org.lzh;
import org.lzh.enumeration.Taste;
//各类汉堡产品记录
public class Burger implements Cloneable{
private int id;//汉堡id
private String name;//汉堡产品名称
private Taste taste;//口味
private Double price;//价格,null表示为赠品
private Bread bread;//汉堡使用的面包片
@Override
protected Object clone() throws CloneNotSupportedException {
//先向上调用到Object的clone()方法做一下浅拷贝
Object obj=super.clone();
//转换成当前的汉堡类型
Burger burger_clone= (Burger) obj;
//此时int id和String name就拷贝好了,不用再考虑它们了
//枚举类Taste,因为枚举永远是单例,也没必要去考虑了
//考虑Double类型的price,做一下拷贝
Double price_clone=new Double(price);//自动解包
//考虑Bread类型的bread,做一下拷贝
Bread bread_clone= (Bread) bread.clone();
//深拷贝:设置拷贝了的属性对象
burger_clone.price=price_clone;
burger_clone.bread=bread_clone;
return burger_clone;
}
}
这里为了方便就不加getter、setter和有参构造了。
使用序列化-反序列化实现深拷贝
将对象序列化成字节序列,会将整个对象图进行序列化(除了transient
修饰的属性),然后再反序列化到一个新对象里就实现了深拷贝。
让前面两个类Burger和Bread实现Serializable
序列化接口,并提供getter、setter、全参constructor和toString()
实现,以测试一下序列化和反序列化来实现深拷贝:
package org.lzh;
import org.lzh.enumeration.Taste;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//构造依赖对象以构造要序列化的burger对象
Bread bread = new Bread(1, "小猫咪面包片");
Burger burger = new Burger(1, "lzh辣堡", Taste.SPICY, new Double("15.5"), bread);
//字节序列-输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//对象输出流,传入字节序列
ObjectOutputStream oos = new ObjectOutputStream(baos);
//将要序列化的汉堡对象写入
oos.writeObject(burger);
//刷新
oos.flush();
//至此,burger对象已经序列化到[字节序列-输出流]中,下面做反序列化
//字节序列-输入流,从字节序列输出流解成字节序列来构造
//即将序列化后的对象读入到本[字节序列-输入流]中
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//对象输入流,传入字节序列
ObjectInputStream ois = new ObjectInputStream(bais);
//读出其中的对象
Burger burger_clone = (Burger) ois.readObject();
//先看看是不是同一个对象
System.out.println(burger == burger_clone);
//再修改一下原来burger的属性
Bread bread_other = new Bread(2, "小狗面包片");
burger.setBread(bread_other);
burger.setId(3);
burger.setName("SB汉堡");
burger.setTaste(Taste.NOTSPICY);
burger.setPrice((double) 21);//自动装箱
//输出一下,看看有没有对深拷贝后的burger_clone产生影响
System.out.println(burger);
System.out.println(burger_clone);
}
}
输出:
false
id:3,name:SB汉堡,taste:NOTSPICY,price21.0,breadid:2,品牌名:小狗面包片
id:1,name:lzh辣堡,taste:SPICY,price15.5,breadid:1,品牌名:小猫咪面包片
补充
直接使用Java的clone()
实现深拷贝会有很多坑(比如调用的属性的clone()
能保证是深拷贝吗),使用序列化实现深拷贝是一种好的方式,也可以考虑使用一些其它工具,如Spring的BeanUtils
工具。