关于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. windows下安装zabbix_agents_2.2.0

    下载与解压 下载zabbix_agents_2.2.0 http://www.zabbix.com/downloads/2.2.0/zabbix_agents_2.2.0.win.zip 解压到C盘下 ...

  2. HNOI2006-鬼谷子的钱袋

    鬼谷子的钱袋 鬼谷子非常聪明,正因为这样,他非常繁忙,经常有各诸侯车的特派员前来向他咨询时政.有一天,他在咸阳游历的时候,朋友告诉他在咸阳最大的拍卖行(聚宝商行)将要举行一场拍卖会,其中有一件宝物引起 ...

  3. 在SQL Server中 获取日期、日期格式转换

    --常用日期转换参数: PRINT CONVERT(varchar, getdate(), 120 ) 2016-07-20 16:09:01 PRINT replace(replace(replac ...

  4. Android中的五大布局和logcat打印日志

    在android中的布局有五大类,有的时候你可能用到一种,但有的时候你也可能需要两种或者三种布局同时一起使用.这五种布局为别为:LinearLayout(线性布局),FrameLayout(框架布局) ...

  5. 表单美化-原生javascript和jQuery多选按钮(兼容IE6)

    前些天我们讲了下单选按钮的美化今天来做表单元素多选按钮的美化.我们的想法是:利用多选按钮是否被选中和是否不给选择的特性来为按钮的父元素添加对应的样式,就是说用什么的样式是由按钮的状态来决定. 用到的图 ...

  6. NTT【51nod】1514 美妙的序列

    题意:1~n 的全排列中,有多少个排列满足任意从中间切成两段后,左边段的最大值大于右边段的最小值? 例如:n为3时有3种 2 3 1 3 1 2 3 2 1 解释:比如 2 3 1 (2) (3 1) ...

  7. 在SSH框架中使用Spring的好处

    在SSH框假中spring充当了管理容器的角色.我们都知道Hibernate用来做持久层,因为它将JDBC做了一个良好的封装,程序员在与数据库进行交互时可以不用书写大量的SQL语句.Struts是用来 ...

  8. 企业信息化快速开发平台JeeSite

    网站:http://jeesite.com/ 可用于企业后台管理

  9. js 中文乱码

    js合成url时,如果参数是中文,传到struts2中会乱码,解决办法如下:1.js文件中使用encodeURI()方法(必须套两层).login_name = encodeURI(encodeURI ...

  10. icp算法的一些参考资料

    1.综述:迭代最近点算法综述,介绍了svd分解和四元数法,其中 svd法:http://blog.csdn.net/kfqcome/article/details/9358853 四元数法:http: ...