java List 深度复制方法

        Java数组的复制有很多方法,但绝大多数是浅复制,下面寻找探索Java数组的复制方法,并验证其是深复制还是浅复制。

        这是下面要频繁使用的一个JavaBean。

package com.bijian.test;

import java.io.Serializable;

class Person implements Serializable {
    
    private int age;  
    private String name;  
      
    public Person(){};  
    public Person(int age,String name){  
        this.age=age;  
        this.name=name;  
    }  
      
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
      
    public String toString(){  
        return this.name+"-->"+this.age;  
    }
}

        后台打印List集合、数组的静态方法类。

package com.bijian.test;

import java.util.List;

public class PrintUtils {

    public static <T> void printList(List<T> list){  
        System.out.println("---begin---");  
        for(T t : list){  
            System.out.println(t);  
        }  
        System.out.println("---end---");  
    }  
    
    public static <T> void printArray(T[] array){  
        System.out.println("---begin---");  
        for(T t : array){  
            System.out.println(t);  
        }  
        System.out.println("---end---");  
    }
}

        这是数据源集合,下面将通过各种方法企图来深复制该List集合中的元素。

public static List<Person> init() {
        
        List<Person> srcList=new ArrayList<Person>();  
        Person p1=new Person(20,"123");  
        Person p2=new Person(21,"ABC");  
        Person p3=new Person(22,"abc");  
        srcList.add(p1);  
        srcList.add(p2);  
        srcList.add(p3);
        
        return srcList;
}

一.遍历循环复制

/**
 * 遍历循环复制
 * @param srcList
 */
public static void test01(List<Person> srcList) {
	
	List<Person> destList=new ArrayList<Person>(srcList.size());  
	for(Person p : srcList){  
		destList.add(p);  
	}  
			  
	PrintUtils.printList(destList);  
	srcList.get(0).setAge(100);  
	PrintUtils.printList(destList);  
}

        上面的代码在add时候,并没有new Person()操作。因此,在srcList.get(0).setAge(100);破坏源数据时,目标集合destList中元素的输出同样受到了影响,原因是浅复制造成的。

        运行结果:

---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---

二.使用List实现类的构造方法

/**
 * 使用List实现类的构造方法
 * @param srcList
 */
public static void test02(List<Person> srcList) {
	
	List<Person> destList=new ArrayList<Person>(srcList);  
	PrintUtils.printList(destList);  
	srcList.get(0).setAge(100);  
	PrintUtils.printList(destList);
}

        通过ArrayList的构造方法来复制集合内容,同样是浅复制,在修改了源数据集合后,目标数据集合对应内容也发生了改变。

        运行结果:

扫描二维码关注公众号,回复: 511921 查看本文章
---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---

三.使用list.addAll()方法

/**
 * 使用list.addAll()方法
 * @param srcList
 */
public static void test03(List<Person> srcList) {
	
	List<Person> destList=new ArrayList<Person>(srcList);  
	PrintUtils.printList(destList);  
	srcList.get(0).setAge(100);  
	PrintUtils.printList(destList);
}

        java.util.list.addAll()方法同样是浅复制,运行结果如下:

---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---

四.使用System.arraycopy()方法

public static void test04(List<Person> srcList) {
	
	Person[] srcPersons=srcList.toArray(new Person[0]);
	Person[] destPersons=new Person[srcPersons.length];  
	System.arraycopy(srcPersons, 0, destPersons, 0, srcPersons.length);  
	//destPersons=srcPersons.clone();
	  
	PrintUtils.printArray(destPersons);  
	srcPersons[0].setAge(100);  
	PrintUtils.printArray(destPersons);  
	  
	List<Person> destList=Arrays.asList(destPersons);  
	PrintUtils.printList(destList);
}

        这种方式虽然比较变态,但是起码证明了System.arraycopy()方法和clone()是不能对List集合进行深复制的。运行结果如下:

---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---

 

五.java.util.Collections.copy()方法

/**
 * 注意:
 * destList的初始化如是填数字,表示的是这个List的容纳能力,并不是说des1中就有了几个元素。
 * 查看api才知 道,它的capacity(容纳能力大小)可以指定(最好指定)。而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。
 * 然而进行copy()时候,首先做的是将desc1的size和src1的size大小进行比较,只有当desc1的 size 大于或者等于src1的size时才进行拷贝,否则抛出IndexOutOfBoundsException异常。
 * 
 * 因此如下实例都是通过Arrays.asList(new Person[srcList.size()])或CollectionUtils.addAll(destList, new Person[srcList.size()]);来进行destList的初始化的
 * 
 * @param srcList
 */
public static void test06(List<Person> srcList) {
	
//        List<Person> destList=new ArrayList<Person>(Arrays.asList(new Person[srcList.size()]));
//        Collections.copy(destList,srcList);
//        PrintUtils.printList(destList); 
//        srcList.get(0).setAge(100);  
//        PrintUtils.printList(destList);
	
	List<Person> destList=new ArrayList<Person>();
	CollectionUtils.addAll(destList, new Person[srcList.size()]);
	Collections.copy(destList, srcList);
	PrintUtils.printList(destList);
	srcList.get(0).setAge(100);  
	PrintUtils.printList(destList);
}

        运行结果如下,还是浅复制。

---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---

 

六.使用序列化方法(相对靠谱的方法)

public static void test05(List<Person> srcList) {
	
	try {
		List<Person> destList = deepCopy(srcList);
		PrintUtils.printList(destList);  
		srcList.get(0).setAge(100);  
		PrintUtils.printList(destList);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
	
	ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
	ObjectOutputStream out = new ObjectOutputStream(byteOut);  
	out.writeObject(src);  
  
	ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());  
	ObjectInputStream in = new ObjectInputStream(byteIn);  
	@SuppressWarnings("unchecked")  
	List<T> dest = (List<T>) in.readObject();
	return dest;  
}

        这是比较靠谱的做法,听说是国外某位程序大师提出来的。实际运行的结果也同样是正确的。运行结果如下:

---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->20
ABC-->21
abc-->22
---end---

        其实,上面这些不靠谱List深复制的做法在某些情况是可行的,这也是为什么有些人说这其中的一些做法是可以实现深复制的原因。哪些情况下是可行(本质上可能还是不靠谱)的呢?比如List<String>这样的情况。我上面使用的是List<Person>,它和List<String>的区别就在于Person类和String类的区别,Person类提供了破坏数据的2个setter方法。因此,在浅复制的情况下,源数据被修改破坏之后,使用相同引用指向该数据的目标集合中的对应元素也就发生了相同的变化。

因此,在需求要求必须深复制的情况下,要是使用上面提到的方法,请确保List<T>中的T类对象是不易被外部修改和破坏的。 

猜你喜欢

转载自bijian1013.iteye.com/blog/2358367
今日推荐