随笔分类
拷贝
concept
对象的拷贝,就是根据原来的对象复制出属性相同、状态一致的新的对象
什么时候来使用拷贝呢?
目前,据我了解的有两种用途:
- 其一,创建具有大量属性相似的对象时,为了简化操作,可以来使用克隆
- 其二,多线程环境下,如果不通过克隆构造出新的对象,线程池中的多个线程会
共用同一个对象
,这便会出现数据的安全问题
因此,有时候,若能够克隆出一个新的对象,并且对新对象的修改不影响原始对象,就能实现我们期待的效果
因此,可以来使用 拷贝 clone
探讨:
深、浅拷贝?
Object类中提供了 clone方法,直接调用 x.clone返回来的是 浅拷贝
我们所希望的是拷贝出来的对象属性、状态和原对象一致,但二者又是相互独立的,而浅拷贝无法做到这一点
浅拷贝与深拷贝的主要区别在于对于引用类型是否共享
如果调用了 clone函数的类没有实现 Cloneable
接口将会抛出 CloneNotSupportedException
clone方法注解中有这么一段描述:使用 clone方法将创建此对象的类的新实例,并且用原对象的属性来去初始化新对象的所有字段,就像是赋值一样,字段本身不会被克隆
;因此,该方法执行的是此对象的浅复制,而不是深复制操作
如果一个类只包含基本类型的属性或者指向不可变对象引用,这种情况下,super.clone()
返回的对象不需要被修改
看个 demo:
public class Item {
public void setId(Long id) {
this.id = id;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public void setName(String name) {
this.name = name;
}
public void setDesc(String desc) {
this.desc = desc;
}
private Long id;
private Long itemId;
private String name;
private String desc;
}
public class Order implements Cloneable{
private Long id;
private String orderNum;
public void setId(Long id) {
this.id = id;
}
public void setOrderNum(String orderNum) {
this.orderNum = orderNum;
}
public void setItems(List<Item> items) {
this.items = items;
}
private List<Item> items;
public static void main(String[] args) throws CloneNotSupportedException {
Order order = new Order();
order.setId(1L);
order.setOrderNum("num1");
Item item = new Item();
item.setId(1L);
item.setItemId(1L);
item.setName("MacBook Pro");
item.setDesc("程序员标配");
List<Item> list = new ArrayList<>();
list.add(item);
order.setItems(list);
// 进行拷贝
// 根据原对象的类创建出了新的实例
Order clone = (Order) order.clone();
System.out.println(order == clone);
// 看, 这里就是引用类型的共享了, 这便是浅拷贝
// 只对
System.out.println(order.items == clone.items);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
可见,这便是浅拷贝
那如何去实现深拷贝呢?前面讲过,浅拷贝的 clone默认调用了 super.clone()
,对于新创建的对象并没有去克隆,只是简单的使用原对象赋值而已 (即引用类型共享)
因此,想要实现深拷贝的话,那么我们就需要对原对象的引用类型也要进行一次 clone才行 (即根据引用类型对应的类创建出新的实例,再去赋值),即我们需要去修改下 clone方法
实现
手动深拷贝
如下 demo:(做了少许更改)
public class Order implements Cloneable{
private Long id;
private String orderNum;
public void setId(Long id) {
this.id = id;
}
public void setOrderNum(String orderNum) {
this.orderNum = orderNum;
}
public void setItems(List<Item> items) {
this.items = items;
}
private List<Item> items;
public static void main(String[] args) throws CloneNotSupportedException {
Order order = new Order();
order.setId(1L);
order.setOrderNum("num1");
Item item = new Item();
item.setId(1L);
item.setItemId(1L);
item.setName("MacBook Pro");
item.setDesc("程序员标配");
List<Item> list = new ArrayList<>();
list.add(item);
order.setItems(list);
// 进行拷贝
// 根据原对象的类创建出了新的实例
Order clone = (Order) order.clone();
System.out.println(order == clone);
// 看, 这里就是引用类型的共享了, 这便是浅拷贝
// 只对
System.out.println(order.items == clone.items);
// 详细验证
for (int i = 0; i < order.items.size(); i ++) {
System.out.println(order.items.get(i).getId() == clone.items.get(i).getId()); // false
System.out.println(order.items.get(i).getItemId() == clone.items.get(i).getItemId()); // false
System.out.println(order.items.get(i).getName() == clone.items.get(i).getName()); // true, 这里无需担心,String不可变,就算是进行了更改,也仅仅是创建出新的 String对象而以,不会影响原本的值.
System.out.println(order.items.get(i).getDesc() == clone.items.get(i).getDesc()); // true
}
}
public Long getId() {
return id;
}
public String getOrderNum() {
return orderNum;
}
public List<Item> getItems() {
return items;
}
// 注意:这里不能直接去使用 item.clone - 如果过 item存在引用类型的话,那么这还是引用类型的共享,即浅拷贝
// 需要手动来进行拷贝 - 有点繁杂
// String 不可变(发生更改时, 实际上会创建出新的 String对象), 一次无需手动 clone
@Override
protected Object clone() throws CloneNotSupportedException {
Order clone = (Order)super.clone();
if (this.id != null) {
clone.setId(new Long(id));
}
if (this.items != null) {
List<Item> list = new ArrayList<>();
for (Item item : items) {
Item itm = new Item();
itm.setId(new Long(item.getId()));
itm.setItemId(new Long(item.getItemId()));
itm.setName(item.getName());
itm.setDesc(item.getDesc());
list.add(itm);
}
clone.setItems(list);
}
return clone;
}
}
序列化
前面讲过,序列化就是将 Java对象变成字节序列,而反序列化是 根据字节序列重建出一个状态一致新的对象
,因此原始对象和反序列后的对象修改互不影响
即,深拷贝
其实就是序列化的重要用途之一
// 序列化实现深拷贝
public Object serialDeepClone() throws IOException, ClassNotFoundException {
// 将原始对象变为字节流 - 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(this);
// 获取输出字节流 - 反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
debug调试如下:
Java的序列化本身似乎比较慢,可以去使用其他的序列化 (目前笔者还不太了解这方面的...)
Summary
不管采取的是哪种拷贝方式,都应该尽量 将其封装至项目的克隆工具类
之中去,方便复用