前言
之前探討過Java數組的深複製問題,現在來說說<一些不靠譜的java.util.List深複製方法>。爲什麼不說<靠譜的深複製方法>呢?因爲在尋找探索<靠譜的深複製方法>的過程中,我發現了這些不靠譜的方法,寫下來是希望給自己和他人提個醒,不要犯這樣的錯誤。
先講的是淺複製,第五條是深度複製
淺複製
這是下面要頻繁使用的一個JavaBean
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集合的一個靜態方法
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集合中的元素
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);
1、遍歷循環複製
ayList<Person>(srcList.size());
for(Person p : srcList){
destList.add(p);
}
printList(destList);
srcList.get(0).setAge(100);
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---
2、使用List實現類的構造方法
List<Person> destList=new ArrayList<Person>(srcList);
printList(destList);
srcList.get(0).setAge(100);
printList(destList);
通過ArrayList的構造方法來複制集合內容,同樣是淺複製,在修改了源數據集合後,目標數據集合對應內容也發生了改變。在查閱資料的過程中,看到有人說這種方式 能實現深複製,其實這是不對的。對於某些特殊的元素,程序運行的結果形似深複製,其實還是淺複製。具體一會兒再說。
後臺打印輸出代碼
---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---
3、使用list.addAll()方法
List<Person> destList=new ArrayList<Person>();
destList.addAll(srcList);
printList(destList);
srcList.get(0).setAge(100);
printList(destList);
java.util.list.addAll()方法同樣是淺複製
後臺打印輸出代碼
---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->100
ABC-->21
abc-->22
---end---
4、使用System.arraycopy()方法
Person[] srcPersons=srcList.toArray(new Person[0]);
Person[] destPersons=new Person[srcPersons.length];
System.arraycopy(srcPersons, 0, destPersons, 0, srcPersons.length);
//destPersons=srcPersons.clone();
printArray(destPersons);
srcPersons[0].setAge(100);
printArray(destPersons);
List<Person> destList=Arrays.asList(destPersons);
printList(destList);
這種方式雖然比較變態,但是起碼證明了System.arraycopy()方法和clone()是不能對List集合進行深複製的。
-------------------------------分割線---------------------------------------------------------------------------
深度複製
5、使用序列化方法(相對靠譜的方法)
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;
}
List<Person> destList=deepCopy(srcList);
printList(destList);
srcList.get(0).setAge(100);
printList(destList);
這是比較靠譜的做法,聽說是國外某位程序大師提出來的。實際運行的結果也同樣是正確的。
後臺打印輸出代碼
---begin---
123-->20
ABC-->21
abc-->22
---end---
---begin---
123-->20
ABC-->21
abc-->22
---end---
其實,上面這些不靠譜List深複製的做法在某些情況是可行的,這也是爲什麼有些人說這其中的一些做法是可以實現深複製的原因。哪些情況下是可行(本質上可能還是不靠譜)的呢?比如List這樣的情況。我上面使用的是List,它和List的區別就在於Person類和String類的區別,Person類提供了破壞數據的2個setter方法。因此,在淺複製的情況下,源數據被修改破壞之後,使用相同引用指向該數據的目標集合中的對應元素也就發生了相同的變化。
因此,在需求要求必須深複製的情況下,要是使用上面提到的方法,請確保List中的T類對象是不易被外部修改和破壞的。