关于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. [HDOJ1231]最大连续子序列

    混了好几个地方的博客,还是觉得博客园比较靠谱,于是决定在这里安家落户了.本人本科生一个,希望各位巨巨多多指教~ Hello World! 单独一个象征性的问候实在是太low了,还是决定来点实质性的.. ...

  2. poj 1066 线段相交

    链接:http://poj.org/problem?id=1066 Treasure Hunt Time Limit: 1000MS   Memory Limit: 10000K Total Subm ...

  3. yii2-datepicker/datetimepicker插件使用

    datepicker: https://github.com/2amigos/yii2-date-picker-widget 通过composer安装: composer require 2amigo ...

  4. [转载] 高效 MacBook 工作环境配置

    原文: http://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=208231200&idx=1&sn=8a76ddc56c1f ...

  5. 初识redis——mac下搭建redis环境

    一.redis简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合)和zset(有 ...

  6. 算法_栈与队列的Java链表实现

    链表是一个递归的数据结构,它或者为null,或者是指向一个结点的引用,该结点含有一个泛型的元素和指向另一个链表的引用.可以用一个内部类来定义节点的抽象数据类型: private class Node ...

  7. Android控件之RadioGroup与RadioButton(单选控件)

    一.RadioGroup与RadioButton 1.什么是RadioGroup: RadioButton的一个集合,提供多选机制 2.什么是RadioButton: RadioButton包裹在Ra ...

  8. Struts2的Action中如何操作作用域对象

    得到作用域对象有三种方法,这里用代码来解释: package com.cy.action; import javax.servlet.ServletContext; import javax.serv ...

  9. 常用的 文件 MIME类型

    估计很多朋友对不同后缀的文件对应的MIME类型不熟悉(实际上这么多我也记不住), 所以将平常常见的一些文件后缀对应的MIME类型写了一个对照表,现在奉献给大家: .asx,video/x-ms-asf ...

  10. Kafka的配置文件详细描述

    在kafka/config/目录下面有3个配置文件: producer.properties consumer.properties server.properties (1).producer.pr ...