小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法
dongangsta 2024-07-13 10:35:01 阅读 87
小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法
前置问题:小问题4 | Arrays.copyOf(elementData, size, a.getClass())是如何实现的?
1. 实现 Cloneable 接口并重写 clone() 方法
对象的 clone 方法默认是浅拷贝,若想实现深拷贝,就需要重写 clone 方法实现属性对象的拷贝。
想实现深拷贝,需要为对象中每一层的每一个对象都实现Clonneable接口(对该接口不熟悉的朋友可以看下面的补充),并重写clone方法,最后在最顶层类的重写的clone方法中,调用所有的clone方法。
简单的说,每层的每个对象都进行浅拷贝=深拷贝。
下面是一个代码示例(在代码示例中,我们省略构造器和getter/setter):
<code>/**
* 用户
*/
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();
}
}
2. 使用序列化和反序列化实现深拷贝
序列化是将对象转换为字节流的过程,以便可以将其保存到磁盘文件、通过网络传输或在内存中保存。
序列化将对象的状态以二进制形式编码,以便稍后可以重新创建对象,这个过程称为反序列化。
序列化在 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();
}
}
执行结果:
3. 自定义实现深拷贝(第三方工具)
除上述的两种拷贝方法之外,还可以自定义实现深拷贝,市面上有一些第三方工具进行了自定义,举个例子:
1. Apache Commons Lang 库提供的 SerializationUtils.clone() 方法,可以对对象进行深拷贝。
(该方法实现模式同2)
<code>User user1 = new User("Alice", originalAddress);
User user2 = (User) SerializationUtils.clone(user1);
2. Apache Commons Lang 库提供的 ObjectUtils.clone() 方法,可以对对象进行深拷贝。
注意此处需要对象实现实现 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序列化的应用场景、序列化方法及代码示例
————————————————————
补充:Cloneable接口的基本原理
Java中的Cloneable接口是一个标记接口,它表示一个类的实例可以被克隆。Cloneable接口的基本原理是通过调用对象的clone()方法来创建一个新的对象,新对象与原对象具有相同的属性值。
Cloneable接口的主要作用是提供一种标准的对象复制机制,它可以用于实现深拷贝或浅拷贝。当一个类实现了Cloneable接口并覆盖了clone()方法时,就可以使用clone()方法来创建对象的副本。
Cloneable接口的优势在于它提供了一种标准的对象复制机制,可以方便地创建对象的副本。但是,它也存在一些缺点,比如需要手动实现clone()方法,并且在实现深拷贝时需要特别注意对象间的引用关系。
Cloneable接口的应用场景包括:
创建对象的副本,以便在不影响原对象的情况下进行操作。实现深拷贝或浅拷贝,根据需要复制对象的属性值。
————————————————————
本专栏是【小问题】系列,旨在每篇解决一个小问题,并秉持着刨根问底的态度解决这个问题可能带出的一系列问题。
上一篇: 史上最全的Python兼职接单挣钱教程,十分详细(附基础教程)
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。