关于java List的深度克隆

List是java容器中最常用的顺序存储数据结构之一。有些时候我们将一组数据取出放到一个List对象中,但是可能会很多处程序要读取他或者是修改他。尤其是并发处理的话,显然有的时候有一组数据有的时候是不够用的。这个时候我们通常会复制出一个甚至多个克隆List来执行更多的操作。

常见的List的克隆方式有很多,下面我们来列举几种常见的List复制的方式:

(首先还是构造一个简单的原始list对象)

List<String> listString0 = new ArrayList<>();
listString0.add("xxx1");
listString0.add("xxx2");
listString0.add("xxx3");

克隆方法1:利用原list作为参数直接构造方法生成。

List<String> listString1 = new ArrayList<>(listString0);

克隆方法2:手动遍历将原listString0中的元素全部添加到复制表中。

List<String> listString2 = new ArrayList<>();
for(int i = 0, l = listString0.size(); i < l; i++)
    listString2.add(listString0.get(i));

克隆方法3:调用Collections的静态工具方法 Collections.copy

List<String> listString3 = new ArrayList<>(Arrays.asList(new String[listString0.size()]));
Collections.copy(listString3,listString0);

注:这种方法本身就有些鬼畜了,因为他需要保证copy双方的List的size值满足一定的条件,而List的size的计算方式......

克隆方法4:使用System.arraycopy方法进行复制

String[] strs = new String[listString0.size()];
System.arraycopy(listString0.toArray(), 0, strs, 0, listString0.size());
List<String> listString4 = Arrays.asList(strs);

注:这种方法就更有些鬼畜了,因为严格来说System.arraycopy是用来copy系统自带的array的,转来转去的效率不多提。

好,先列举这么多,我们来测试一下我们想要的复制有没有达到效果:
我们试着改变listString0的某一个元素的值,如果其他列表中的值没有受到影响那么就是复制成功了。

listString0.set(0, "rock");
listString1.set(2, "deria");

for(int i = 0, l = listString0.size(); i < l; i++)
{
    System.out.println("listString0的第"+i+"个值为:"+listString0.get(i));
    System.out.println("listString1的第"+i+"个值为:"+listString1.get(i));
    System.out.println("listString2的第"+i+"个值为:"+listString2.get(i));
    System.out.println("listString3的第"+i+"个值为:"+listString3.get(i));
    System.out.println("listString4的第"+i+"个值为:"+listString4.get(i));
    System.out.println("------------------------------------------------");
}        

输出结果:
listString0的第0个值为:rock
listString1的第0个值为:xxx1
listString2的第0个值为:xxx1
listString3的第0个值为:xxx1
listString4的第0个值为:xxx1
------------------------------------------------
listString0的第1个值为:xxx2
listString1的第1个值为:xxx2
listString2的第1个值为:xxx2
listString3的第1个值为:xxx2
listString4的第1个值为:xxx2
------------------------------------------------
listString0的第2个值为:xxx3
listString1的第2个值为:deria
listString2的第2个值为:xxx3
listString3的第2个值为:xxx3
listString4的第2个值为:xxx3
------------------------------------------------

看来这几个方法都实现了list的复制,达到了我们想要的效果。修改本体并没有影响到复制体。但是故事远远没有结束。我们来试试下边的例子:
我们创建一个Pojo类:

import java.io.Serializable;

public class PojoStr implements Serializable
{
    /**
     *
     */
    private static final long serialVersionUID = 4394836462951175834L;

    private String str = "";

    public String getStr()
    {
        return str;
    }

    public void setStr(String str)
    {
        this.str = str;
    }
}

这个Pojo类只存储了一个字符串,同样可以模拟我们之前的几种复制方法,代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class TestPojo
{
    public static void main(String[] args)
    {
        List<PojoStr> listPojoStr0 = new ArrayList<>();
        PojoStr p1 = new PojoStr();
        p1.setStr("xxx1");
        listPojoStr0.add(p1);
        PojoStr p2 = new PojoStr();
        p2.setStr("xxx2");
        listPojoStr0.add(p2);
        PojoStr p3 = new PojoStr();
        p3.setStr("xxx3");
        listPojoStr0.add(p3);

        List<PojoStr> listPojoStr1 = new ArrayList<>(listPojoStr0);

        List<PojoStr> listPojoStr2 = new ArrayList<>();
        for(int i = 0, l = listPojoStr0.size(); i < l; i++)
            listPojoStr2.add(listPojoStr0.get(i));

        List<PojoStr> listPojoStr3 = new ArrayList<>(Arrays.asList(new PojoStr[listPojoStr0.size()]));
        Collections.copy(listPojoStr3,listPojoStr0);

        PojoStr[] strs = new PojoStr[listPojoStr0.size()];
        System.arraycopy(listPojoStr0.toArray(), 0, strs, 0, listPojoStr0.size());
        List<PojoStr> listPojoStr4 = Arrays.asList(strs);

        listPojoStr0.get(0).setStr("rock");

        for(int i = 0, l = listPojoStr0.size(); i < l; i++)
        {
            System.out.println("listPojoStr0的第"+i+"个值为:"+listPojoStr0.get(i).getStr());
            System.out.println("listPojoStr1的第"+i+"个值为:"+listPojoStr1.get(i).getStr());
            System.out.println("listPojoStr2的第"+i+"个值为:"+listPojoStr2.get(i).getStr());
            System.out.println("listPojoStr3的第"+i+"个值为:"+listPojoStr3.get(i).getStr());
            System.out.println("listPojoStr4的第"+i+"个值为:"+listPojoStr4.get(i).getStr());
            System.out.println("------------------------------------------------");
        }
    }
}

运行后我们居然惊讶的发现:

listPojoStr0的第0个值为:rock
listPojoStr1的第0个值为:rock
listPojoStr2的第0个值为:rock
listPojoStr3的第0个值为:rock
listPojoStr4的第0个值为:rock
------------------------------------------------
由于本体表的某个数据的修改,导致后续的克隆表的数据全被修改了。而且四种方法全部阵亡......也就是说对于自定义POJO类而言上述的四种方法都未能实现对元素对象自身的复制。复制的只是对象的引用或是说地址。

我们知道List自身是一个对象,他在存储类类型的时候,只负责存储地址。而存储基本类型的时候,存储的就是实实在在的值。其实上边的程序也说明了这点,因为我们修改PojoStr-List的时候直接的修改了元素本身而不是使用的ArrayList的set(index,object)方法。所以纵然你有千千万万个List,元素还是那么几个。无论是重新构造,Collections的复制方法,System的复制方法,还是手动去遍历,结果都一样,这些方法都只改变了ArrayList对象的本身,简单的添加了几个指向老元素的地址。而没做深层次的复制。(压根没有没有 new新对象 的操作出现。)
当然有的时候我们确实需要将这些元素也都复制下来而不是只是用原来的老元素。然而很难在List层实现这个问题。毕竟依照java的语言风格,也很少去直接操作这些埋在堆内存中的数据,所有的操作都去针对能找到他们的地址了。地址没了自身还会被GC干掉。所以只好一点点的去遍历去用new创建新的对象并赋予原来的值。不过据说国外的某位大神可能觉得上述的做法略微鬼畜,所以巧用序列化对象让这些数据在IO流中360度跑了一圈,居然还真的成功复制了。其实把对象序列化到流中,java语言实在是妥协了,毕竟这把你不能再把地址给我扔进去吧?再说了io流是要和别的系统交互的,你发给别人一个地址让别人去哪个堆里找?所以不用多提肯定要新开辟堆内存的。
方法如下:(注:前提是 T如果是Pojo类的话,必须实现序列化接口,这是对象进入IO流的基本要求)。

@SuppressWarnings("unchecked")
public static <T> List<T> deepCopyList(List<T> src)
{
    List<T> dest = null;
    try
    {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(src);
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        dest = (List<T>) in.readObject();
    }
    catch (IOException e)
    {

    }
    catch (ClassNotFoundException e)
    {

    }
    return dest;
}

来试一下:

List<PojoStr> listPojoStr5 = CopyUtils.deepCopyList(listPojoStr0);
listPojoStr0.get(0).setStr("rock");

输出结果
listPojoStr0的第0个值为:rock
listPojoStr1的第0个值为:rock
listPojoStr2的第0个值为:rock
listPojoStr3的第0个值为:rock
listPojoStr4的第0个值为:rock
listPojoStr5的第0个值为:xxx1

OK 成功

Java List的深度克隆的更多相关文章

  1. JAVA对象的深度克隆

    有时候,我们需要把对象A的所有值复制给对象B(B = A),但是这样用等号给赋值你会发现,当B中的某个对象值改变时,同时也会修改到A中相应对象的值! 也许你会说,用clone()不就行了?!你的想法只 ...

  2. java中传值及引伸深度克隆的思考(说白了Java只能传递对象指针)

    java中传值及引伸深度克隆的思考 大家都知道java中没有指针.难道java真的没有指针吗?句柄是什么?变量地址在哪里?没有地址的话简直不可想象! java中内存的分配方式有两种,一种是在堆中分配, ...

  3. java对象 深度克隆(不实现Cloneable接口)和浅度克隆

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt128 为什么需要克隆: 在实际编程过程中,我们常常要遇到这种情况:有一个对象 ...

  4. 如何复制一个java对象(浅克隆与深度克隆)

    在项目中,有时候有一些比较重要的对象经常被当作参数传来传去,和C语言的值传递不同,java语言的传递都是引用传递,在任何一个地方修改了这个对象的值,就会导致这个对象在内存中的值被彻底改变.但是很多时候 ...

  5. Java中深度克隆和浅度克隆

    一:使用目的: 就是为了快速构造一个和已有对象相同的副本.如果需要克隆对象,一般需要先创建一个对象,然后将原对象中的数据导入到新创建的对象中去,而不用根据已有对象进行手动赋值操作. 二:Object中 ...

  6. Java的深度克隆和浅度克隆

    说到克隆,其实是个比较简单的概念,跟现实生活正的克隆一样,复制一个一模一样的对象出来.clone()这个方法是从Object继承下来的,一个对象要实现克隆,需要实现一个叫做Cloneable的接口,这 ...

  7. 反射实现java深度克隆

    一.克隆 有时想得到对象的一个复制品,该复制品的实体是原对象实体的克隆.复制品实体的变化不会引起原对象实体发生变化,这样的复制品称为原对象实体的克隆对象或简称克隆. 1.浅复制(浅克隆) 概念:被复制 ...

  8. Java的赋值、浅克隆和深度克隆的区别

    赋值 直接  = ,克隆 clone 假如说你想复制一个简单变量.很简单: int a= 5; int b= a; b = 6; 这样 a == 5, b == 6 不仅仅是int类型,其它七种原始数 ...

  9. 原型模式 —— Java的赋值、浅克隆和深度克隆的区别

    赋值 直接  = ,克隆 clone 假如说你想复制一个简单变量.很简单: int a= 5; int b= a; b = 6; 这样 a == 5, b == 6 不仅仅是int类型,其它七种原始数 ...

随机推荐

  1. MusigCV安装

    首先,将下载的安装文件zip包,http://www.mathworks.com/products/compiler/mcr/ MCR2013a 然后依次执行下面的命令: 进入目录:cd /tmp 解 ...

  2. SQL疑难杂症【3】链接服务器提示"无法启动分布式事物"

    今天接到用户反馈,应用系统出现异常,无法正常使用,于是用Profiler跟踪了一下语句,发现执行的存储过程中调用了链接服务器,做了一些跨服务器操作数据的动作,刚好就是这个链接服务器出错了,错误截图如下 ...

  3. C语言程序设计现代方法_基本类型(第七章)

    C语言支持两种不同的数值类型,整数类型,浮点类型. C语言的整数类型有不同的尺寸.int类型通常为32位,但在老的CPU上可能是16位.有些可能是64位. 因此,int型如果在16位CPU上最大值就是 ...

  4. ubuntu 16.04 安装 QQ

    需要在Ubuntu 16.04下使用QQ,查找了一下,知乎的办法可行. 参考了:http://www.zhihu.com/question/20176925 与 http://www.zhihu.co ...

  5. springaop实现登陆验证

    1.首先配置好springmvc和springaop 2.先写好登陆方法,通过注解写代理方法 通过代理获得登陆方法的参数方法名,然后再aop代理方法内进行登陆验证 贴出代码 package com.h ...

  6. Web缓存的作用与类型

    前言 Web缓存是指一个Web资源(如html页面,图片,js,数据等)存在于Web服务器和客户端(浏览器)之间的副本.缓存会根据进来的请求保存输出内容的副本:当下一个请求来到的时候,如果是相同的UR ...

  7. hdu 2063 过山车(二分图最佳匹配)

    经典的二分图最大匹配问题,因为匈牙利算法我还没有认真去看过,想先试试下网络流的做法,即对所有女生增加一个超级源,对所有男生增加一个超级汇,然后按照题意的匹配由女生向男生连一条边,跑一个最大流就是答案( ...

  8. return

    return作为返回关键字,有以下两种意义的返回格式: 1,返回把握与函数成果:停止函数执行,返回调用函数,并且把函数的值作为返回成果. turn只能退出当前函数,如果多个函数嵌套就不行了,要想整个退 ...

  9. [CSS] vertical-align

    原文地址: http://www.zhangxinxu.com/wordpress/2010/05/%E6%88%91%E5%AF%B9css-vertical-align%E7%9A%84%E4%B ...

  10. linux套件安装过程中configure,make,make install的作用

    ./configure,make,make install都是典型的使用GNU的AUTOCONF和AUTOMAKE产生的程序的安装步骤.其中: ./configure是检测程序文件,用来检测你的安装平 ...