关于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 Internals学习笔记(二)系统架构

    参考资料: 1. <Windows Internals> 2. http://bestcbooks.com 3. Windows Drive Kit 4. Microsoft Window ...

  2. FLASH CC 2015 CANVAS (六)如何像FLASH那样实现场景(多canvas)

    注意 此系列贴 为个人边“开荒”边写,所以不保证就是最佳做法,也难免有错误! 正式教程会在后续开始更新. swf 项目中,我们可以很容易在一个fla文档里创建多场景.也可以通过多个fla文件发布多个s ...

  3. Spring XML配置实现AOP

    1:  首先我们要定义 配置成切面的类 package cn.gbx.example; import org.aspectj.lang.ProceedingJoinPoint; import org. ...

  4. web工程中各类地址写法的总结

    首先打一个"/" //如果地址给服务器用,那么"/"就代表该web应用 , 如果给浏览器用的,那么"/"就代表网站(其下有多个web应用) ...

  5. iOS - OC NSEnumerator 迭代器

    前言 @interface NSEnumerator<ObjectType> : NSObject <NSFastEnumeration> Xcode 7 对系统中常用的一系列 ...

  6. iOS - Swift NSDate 时间

    前言 NSDate public class NSDate : NSObject, NSCopying, NSSecureCoding NSDate 用来表示公历的 GMT 时间(格林威治时间).是独 ...

  7. mysql 并发控制

    1.多个线程同时修改数据,存在数据不一致的情况,也就是并发控制的问题.2.mysql提供读锁和写锁,读锁之上可以再加读锁,不能加写锁,而写锁之上不能加任何锁.也就是说,读锁是共享的,写锁是排他的.3. ...

  8. mysql 1030 Got error 28 from storage engine

    mysql 1030 Got error 28 from storage engine 错误原因:磁盘临时空间不够. 解决办法:df -h 查看设备存储的使用情况 du -h --max-depth= ...

  9. D3.js 交互式操作

    与图表的交互,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应. 一.什么是交互 交互,指的是用户输入了某种指令,程序接受到指令之后必须做出某种响应.对可视化图表来说,交互能使图表更加 ...

  10. Android布局_相对布局RelativeLayout

    一.RelativeLayout(相对布局)概述 RelativeLayout是相对布局控件,它包含的子控件将以控件之间的相对位置或者子类控件相对父类容器的位置的方式排列 二.RelativeLayo ...