赞
踩
前置问题:小问题4 | Arrays.copyOf(elementData, size, a.getClass())是如何实现的?
对象的 clone 方法默认是浅拷贝,若想实现深拷贝,就需要重写 clone 方法实现属性对象的拷贝。
想实现深拷贝,需要为对象中每一层的每一个对象都实现Clonneable接口(对该接口不熟悉的朋友可以看下面的补充),并重写clone方法,最后在最顶层类的重写的clone方法中,调用所有的clone方法。
简单的说,每层的每个对象都进行浅拷贝=深拷贝。
下面是一个代码示例(在代码示例中,我们省略构造器和getter/setter):
/** * 用户 */ public class User implements Cloneable { private String name; private Address address; @Override public User clone() throws CloneNotSupportedException { User user = (User) super.clone(); user.setAddress(this.address.clone()); return user; } }
/**
* 地址
*/
public class Address implements Cloneable {
private String city;
private String country;
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
序列化是将对象转换为字节流的过程,以便可以将其保存到磁盘文件、通过网络传输或在内存中保存。
序列化将对象的状态以二进制形式编码,以便稍后可以重新创建对象,这个过程称为反序列化。
序列化在 Java 中有许多应用,其中之一是实现深拷贝。
让我们使用和上文相同的两个类,这次让他们实现Serializable :
/** * 用户 */ public class User implements Serializable { private String name; private Address address; // 省略构造器和getter/setter } /** * 地址 */ public class Address implements Serializable { private String city; private String country; // 省略构造器和getter/setter }
然后我们在代码中实现通过序列化和反序列化对对象的拷贝:
public static <T> T deepClone(T obj) throws IOException, ClassNotFoundException { //序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(obj); out.close(); //反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bis); T clone = (T) in.readObject(); in.close(); return clone; } public static void main(String[] args) { Address originalAddress = new Address("New York","USA"); User originalUser = new User("Alice", originalAddress); try { User clonedUser = deepClone(originalUser); System.out.printf("克隆前User:%s,地址:%s - %s %n",originalUser.getName(),originalUser.getAddress().getCity(),originalUser.getAddress().getCountry()); System.out.printf("克隆后User:%s,地址:%s - %s %n",clonedUser.getName(),clonedUser.getAddress().getCity(),clonedUser.getAddress().getCountry()); clonedUser.setName("Bob"); clonedUser.getAddress().setCity("Taipei"); clonedUser.getAddress().setCountry("China"); System.out.printf("修改后User:%s,地址:%s - %s %n",clonedUser.getName(),clonedUser.getAddress().getCity(),clonedUser.getAddress().getCountry()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }
执行结果:
除上述的两种拷贝方法之外,还可以自定义实现深拷贝,市面上有一些第三方工具进行了自定义,举个例子:
(该方法实现模式同2)
User user1 = new User("Alice", originalAddress);
User user2 = (User) SerializationUtils.clone(user1);
注意此处需要对象实现实现 Cloneable 接口并重写 clone() 方法,可以注意到该方法实现了对Array的复制,在单个对象的复制时,是通过反射调用clone方法实现的。
User user3 = ObjectUtils.clone(user1);
贴个ObjectUtils.clone() 方法源码:
public static <T> T clone(T obj) { if (!(obj instanceof Cloneable)) { return null; } else { Object result; if (obj.getClass().isArray()) { Class<?> componentType = obj.getClass().getComponentType(); if (componentType.isPrimitive()) { int length = Array.getLength(obj); result = Array.newInstance(componentType, length); while(length-- > 0) { Array.set(result, length, Array.get(obj, length)); } } else { result = ((Object[])((Object[])obj)).clone(); } } else { try { Method clone = obj.getClass().getMethod("clone"); result = clone.invoke(obj); } catch (NoSuchMethodException var4) { throw new CloneFailedException("Cloneable type " + obj.getClass().getName() + " has no clone method", var4); } catch (IllegalAccessException var5) { throw new CloneFailedException("Cannot clone Cloneable type " + obj.getClass().getName(), var5); } catch (InvocationTargetException var6) { throw new CloneFailedException("Exception cloning Cloneable type " + obj.getClass().getName(), var6.getCause()); } } return result; } }
此处有朋友可能注意到,Array.set和Array.getLength两个方法,并好奇Array中的其他方法。
在看了第二种方法,通过序列化实现深拷贝后,有朋友可能也会对序列化的应用产生好奇。
关于Array的源码解释和序列化到底都有什么作用,我们带着问题继续看:
小问题7 | Java序列化的应用场景、序列化方法及代码示例
————————————————————
Java中的Cloneable接口是一个标记接口,它表示一个类的实例可以被克隆。Cloneable接口的基本原理是通过调用对象的clone()方法来创建一个新的对象,新对象与原对象具有相同的属性值。
Cloneable接口的主要作用是提供一种标准的对象复制机制,它可以用于实现深拷贝或浅拷贝。当一个类实现了Cloneable接口并覆盖了clone()方法时,就可以使用clone()方法来创建对象的副本。
Cloneable接口的优势在于它提供了一种标准的对象复制机制,可以方便地创建对象的副本。但是,它也存在一些缺点,比如需要手动实现clone()方法,并且在实现深拷贝时需要特别注意对象间的引用关系。
Cloneable接口的应用场景包括:
————————————————————
本专栏是【小问题】系列,旨在每篇解决一个小问题,并秉持着刨根问底的态度解决这个问题可能带出的一系列问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。