小问题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接口的应用场景包括:

创建对象的副本,以便在不影响原对象的情况下进行操作。实现深拷贝或浅拷贝,根据需要复制对象的属性值。

————————————————————

本专栏是【小问题】系列,旨在每篇解决一个小问题,并秉持着刨根问底的态度解决这个问题可能带出的一系列问题。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。