天天看點

Java List 深度複制方法前言淺複制深度複制

前言

之前探讨過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(,"123");  
Person p2=new Person(,"ABC");  
Person p3=new Person(,"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().setAge();  
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().setAge();  
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().setAge();  
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[]);  
Person[] destPersons=new Person[srcPersons.length];  
System.arraycopy(srcPersons, , destPersons, , srcPersons.length);  
//destPersons=srcPersons.clone();  

printArray(destPersons);  
srcPersons[].setAge();  
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().setAge();  
printList(destList);  
           

這是比較靠譜的做法,聽說是國外某位程式大師提出來的。實際運作的結果也同樣是正确的。

背景列印輸出代碼

---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類對象是不易被外部修改和破壞的。

另一個問題

上面實體類是實作了Serializable接口,那麼使用上面方法可以實作深度複制,但是如果是實作了Android中的Parcelable接口,上面方法就不行了!

那麼一個比較統一的且可以實作深度複制的方法就是:将我們的

List<T>

轉換為json,再把json轉回

List<T>

這裡我們使用Gson來轉換:

public static <T> ArrayList<T> jsonToArrayList(String json, Class<T> clazz) {
        Type type = new TypeToken<ArrayList<JsonObject>>() {
        }.getType();
        ArrayList<JsonObject> jsonObjects = new Gson().fromJson(json, type);

        ArrayList<T> arrayList = new ArrayList<>();
        for (JsonObject jsonObject : jsonObjects) {
            arrayList.add(new Gson().fromJson(jsonObject, clazz));
        }
        return arrayList;
    }
           

使用的時候:

Gson gson = new Gson();
String jsonTran = gson.toJson(sourceList);
ArrayList<T> deepCloneList= GsonUtil.jsonToArrayList(jsonTran, T.class);
           

這樣deepCloneList就是源list深度複制的list了!且修改源list,并不影響複制之後的list!