关于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. Beaglebone Black– 智能家居控制系统 LAS - 网页服务器 Node.js 、Web Service、页面 和 TCP 请求转 UDP 发送

    上一篇,纯粹玩 ESP8266,写入了 init.lua 能收发 UDP.这次拿 BBB 开刀,用 BBB host 一个 web server ,用于与用户交互,数据来自 ESP8266 的 UDP ...

  2. java传递和返回对象

    java传递的只是一个引用,一定要注意准确认识在对象传递和赋值时所发生的一切. 事实上,java中的每个对象(除了基本数据类型以外)的标识符都属于指针的一种,但是其使用受到了严格的限制和防范,不仅在编 ...

  3. poj 3122 (二分查找)

    链接:http://poj.org/problem?id=3122 Pie Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 1 ...

  4. 转 cocos2dx内存优化 (之二)

    一.cocos2dx之如何优化内存使用(高级篇) 本文由qinning199原创,转载请注明:http://www.cocos2dx.net/?p=93 一.内存优化原则 为了优化应用内存,你应该知道 ...

  5. Maven仓库构建

    什么是Maven仓库 在不用Maven的时候,比如说以前我们用Ant构建项目,在项目目录下,往往会看到一个名为/lib的子目录,那里存放着各类第三方依赖jar文件,如log4j.jar,junit.j ...

  6. jj前端项目1th总结

    1:设计图--->分出几个独立模块--->颗粒化布局--->文档流控制整体布局--->固定位置的元素绝对定位,段落这种元素不可绝对定位.----->加上和后台交互用的js ...

  7. Javascript中typeof instanceof constructor的区别

    typeof typeof,是一个运算符,运算中需要一个操作数,运算的结果就是这个操作数的类型,运算的结果是一个字符串.他有一定的局限性,对于对象类型的值,只能得到一个object结果,却不能精确得到 ...

  8. spring中的Log4jConfigListener作用和webapp.root的设置

    转:http://blog.sina.com.cn/s/blog_7bbf356c01016wld.html 使用spring中的Log4jConfigListener有如如下好处:     1. 动 ...

  9. MySQL在windows系统中修改datadir路径后无法启动问题,报错1067

    windows server2008下如何更改MySQL数据库的目录的帖子已经很多了,这里简单介绍一个步骤,如果不成功请先查看其它帖子. 更改默认的mysql数据库目录 将 C:\Documents ...

  10. JavaScript 同名方法的处理

    在JS中,如果存在同名同参的方法,它会先调用哪一个?先看两个例子: 例1: <html> <head> <title></title> <scri ...