当前位置:   article > 正文

小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法_java 深拷贝

java 深拷贝

小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法

前置问题:小问题4 | Arrays.copyOf(elementData, size, a.getClass())是如何实现的?

1. 实现 Cloneable 接口并重写 clone() 方法

对象的 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;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
/**
 * 地址
 */
public class Address implements Cloneable {

    private String city;
    private String country;

    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

然后我们在代码中实现通过序列化和反序列化对对象的拷贝:

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();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

执行结果:
运行结果

3. 自定义实现深拷贝(第三方工具)

除上述的两种拷贝方法之外,还可以自定义实现深拷贝,市面上有一些第三方工具进行了自定义,举个例子:

1. Apache Commons Lang 库提供的 SerializationUtils.clone() 方法,可以对对象进行深拷贝。

(该方法实现模式同2)

User user1 = new User("Alice", originalAddress);
User user2 = (User) SerializationUtils.clone(user1);
  • 1
  • 2
2. Apache Commons Lang 库提供的 ObjectUtils.clone() 方法,可以对对象进行深拷贝。

注意此处需要对象实现实现 Cloneable 接口并重写 clone() 方法,可以注意到该方法实现了对Array的复制,在单个对象的复制时,是通过反射调用clone方法实现的。

User user3 = ObjectUtils.clone(user1);
  • 1

贴个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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

此处有朋友可能注意到,Array.set和Array.getLength两个方法,并好奇Array中的其他方法。

在看了第二种方法,通过序列化实现深拷贝后,有朋友可能也会对序列化的应用产生好奇。

关于Array的源码解释和序列化到底都有什么作用,我们带着问题继续看:
小问题7 | Java序列化的应用场景、序列化方法及代码示例

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

补充:Cloneable接口的基本原理

Java中的Cloneable接口是一个标记接口,它表示一个类的实例可以被克隆。Cloneable接口的基本原理是通过调用对象的clone()方法来创建一个新的对象,新对象与原对象具有相同的属性值。

Cloneable接口的主要作用是提供一种标准的对象复制机制,它可以用于实现深拷贝或浅拷贝。当一个类实现了Cloneable接口并覆盖了clone()方法时,就可以使用clone()方法来创建对象的副本。

Cloneable接口的优势在于它提供了一种标准的对象复制机制,可以方便地创建对象的副本。但是,它也存在一些缺点,比如需要手动实现clone()方法,并且在实现深拷贝时需要特别注意对象间的引用关系。

Cloneable接口的应用场景包括:

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/663899
推荐阅读
相关标签
  

闽ICP备14008679号