java的深度拷贝实现方式

  • Post author:
  • Post category:java




浅拷贝&深度拷贝


  1. 对象的克隆


    如果想修改一个对象但又不想影响其他引用,可以使用clone()方法。虽然Object定义了克隆方法,但是并不是所有类都具备克隆能力。要想能够执行clone()方法,需要实现Cloneable接口。如果不实现该接口则会抛出CloneNotSupportedException异常。

  2. 浅拷贝


    在拷贝过程中,只拷贝基本类型和引用,引用中对象的值则不会被拷贝。 对象的克隆一般都是浅拷贝,比如ArrayList的clone就是浅拷贝。
 /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

在ArrayList的clone实现的是shallow copy(浅拷贝)。手写一段简单代码测试一下:

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<User> list = new ArrayList<>();
        User user1 = new User();
        user1.setAge("18");
        user1.setName("white");
        list.add(user1);
        ArrayList<User> list1 = (ArrayList<User>) list.clone();
        user1.setName("green");
        list1.forEach(i-> System.out.println(i.getName()));
    }

以上代码输出结果为:green

虽然list1是list的克隆,但是浅拷贝只复制了引用,因此修改list的user仍然会影响到list1。如果想彻底是两个list不产生相互影响就需要进行深度拷贝。


  1. 深度拷贝


    不仅拷贝基本类型,引用中的对象也拷贝则成为深度拷贝。java的克隆方法不提供深度拷贝。需要自己根据实际情况自己来实现。



拷贝的实现方式

除了上面提到的实现Cloneable接口外,还有第三方提供的一些拷贝方式可以进行参考。


1. BeanUtils


spring提供了BeanUtils来实现对象的克隆,基本原理大体上就是使用反射机制来实现的。BeanUtils实现的并不是深度拷贝,不过BeanUtils可以拷贝两个不同的对象,这一点还是很不错的。


2. 序列化拷贝


如果想实现深度拷贝,最简单的方式我觉得是通过序列化的方式来实现。序列化有多种方式,我在这里使用JSON来进行序列化。大体思路是将对象转换成JSON,然后再将json转换成指定对象。

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<User> list = new ArrayList<>();
        User user1 = new User();
        user1.setAge("18");
        user1.setName("white");
        list.add(user1);
        String json = JSON.toJSONString(list);
        List<User> list2 = JSON.parseArray(json,User.class);
        user1.setName("green");
        list2.forEach(i-> System.out.println(i.getName()));
    }

结果为:white 修改list的user不会影响到list2。

通过序列化进行拷贝的方式效率很低,在使用时需要三思而后行。如何可以通过浅拷贝实现就尽量不要深度拷贝,如果可以手动拷贝的就尽量不要用序列化进行拷贝。以免造成不必要的性能浪费。



进一步的思考

String是Java中的对象,为什么进行克隆以后String并不会被影响呢?浅拷贝只会拷贝String引用不是吗?在《java编程思想》里将String定义为“只读类”或者叫做“恒常对象”。简单的说就是这种对象不提供修改的方法,因此就无需担心其他的引用对其的影响了。同时它的缺陷也非常明显,如果要修改内容就只能重新创建新的对象,这加大了内存的开销同时会频繁的引发垃圾回收,代价是比较高昂的。

因此提出了可以被修改的伴随类,伴随类在使用过程中可以任意修改,修改完成后再转换成只读类,StringBuffer就是一个伴随类,toString()就是转换成只读类的过程。



版权声明:本文为u013623958原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。