簡體   English   中英

Immutable Object with ArrayList 成員變量 - 為什么這個變量可以改變?

[英]Immutable Object with ArrayList member variable - why can this variable be changed?

我有一個帶有各種成員變量的 class。 有構造函數和 getter 方法,但沒有 setter 方法。 其實這個object應該是不可變的。

public class Example {
   private ArrayList<String> list; 
}

現在我注意到以下幾點:當我使用 getter 方法獲取變量列表時,我可以添加新值等等 - 我可以更改ArrayList 當我下次為此變量調用get()時,返回更改后的ArrayList 怎么會這樣? 我沒有再設置它,我只是在努力! 對於String ,這種行為是不可能的。 那么這里有什么區別呢?

僅僅因為列表的引用是不可變的並不意味着它引用的列表是不可變的。

即使listfinal也可以這樣做

// changing the object which list refers to
example.getList().add("stuff");

但這不允許的:

// changing list
example.list = new ArrayList<String>();   // assuming list is public

為了使列表不可變(也防止第一行),我建議你使用Collections.unmodifiableList

public class Example {
    final private ArrayList<String> list;

    Example(ArrayList<String> listArg) {
        list = Collections.unmodifiableList(listArg);
    }
}

(請注意,這會創建一個不可修改的列表視圖。如果有人持有原始引用,那么仍然可以通過該列表修改列表。)


使用String,這種行為是不可能的。 那么這里的區別是什么?

這是因為String已經是不可變的(不可修改的)就像列表將其轉換為不可修改的List一樣。

比較:

              String data structure  | List data structure
           .-------------------------+------------------------------------.
Immutable  | String                  | Collection.unmodifiableList(...)   |
-----------+-------------------------+------------------------------------|
Mutable    | StringBuffer            | ArrayList                          |
           '-------------------------+------------------------------------'

您將返回對list的引用。 並且list不是一成不變的。


如果您不希望更改列表,請返回它的副本:

public List<String> get() {
    return new ArrayList<String>(this.list);
}

或者您可以返回不可修改的列表

public List<String> get() {
    return Collections.unmodifiableList(this.list);
}

關鍵是要了解您沒有更改字符串 - 您正在更改列表包含的字符串引用

換句話說:如果我從你的錢包中取出一美元,並用一角硬幣替換它,我沒有改變美元或硬幣 - 我只是改變了錢包的內容。

如果要在列表中使用只讀視圖,請查看Collections.unmodifiableList 這不會停止,它的距離,當然改變包裝清單,但它會阻止任何人誰只有從修改的內容為不可修改的列表的引用。

對於一個真正不可變的列表,請查看GuavaImmutableList類。

Collections.unmodifiableList()使列表無法定義。 這又創建了一個新的最終數組列表並覆蓋了add,remove,addall和clear方法以拋出unsupportedoperationexception。 它是Collections類的黑客。 但是在編譯時它不會阻止你添加和刪除東西。 我寧願選擇克隆該列表。 這可以幫助我保持現有對象不可變,並且不會花費我創建新列表。 閱讀克隆和新運算符之間的區別( http://www.javatpoint.com/object-cloning )。 還有助於在運行時崩潰我的代碼。

正如其他答案所說,從getter返回的Object仍然是可變的。

您可以通過使用Collections類對其進行裝飾來使List不可變:

 list = Collections.unmodifiableList(list);

如果將此內容返回給客戶,則無法向其添加或刪除元素。 然而,他們仍然可以從列表中獲取元素 - 所以你必須確保它們也是不可變的,如果那就是你所追求的!

要獲得一個真正不可變的列表,您必須制作列表內容的深層副本。 UnmodifiableList只會使引用列表有些不可變。 現在制作List或數組的深層副本對於內存越來越大而言將是艱難的。 您可以使用序列化/反序列化並將數組/列表的深層副本存儲到臨時文件中。 由於成員變量需要不可變,因此setter將不可用。 getter會將成員變量序列化為一個文件,然后對其進行desialize以獲得一個深層副本。 Seraialization具有進入對象樹深處的天生特性。 這樣可以確保在某些性能成本下完全不變。

package com.home.immutable.serial;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public final class ImmutableBySerial {

    private final int num;
    private final String str;
    private final TestObjSerial[] arr;

    ImmutableBySerial(int num, String str, TestObjSerial[] arr){
        this.num = num;
        this.str = str;
        this.arr = getDeepCloned(arr);
    }

    public int getNum(){
        return num;
    }

    public String getStr(){
        return str;
    }

    public TestObjSerial[] getArr(){
        return getDeepCloned(arr);
    }

    private TestObjSerial[] getDeepCloned(TestObjSerial[] arr){
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        TestObjSerial[] clonedObj = null;
        try {
             fos = new FileOutputStream(new File("temp"));
             oos = new ObjectOutputStream(fos);
             oos.writeObject(arr);
             fis = new FileInputStream(new File("temp"));
             ois = new ObjectInputStream(fis);
             clonedObj = (TestObjSerial[])ois.readObject();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clonedObj;
    }
}

因此,如果要保護列表不被更改,則不應為列表提供getter方法。

它的對象仍然保持完整,因為你沒有setter。 但你要做的是刪除/添加新的/不同的對象,這很好。

列表引用是不可變的,但不是列表。 如果您希望列表本身是不可變的,請考慮使用ImmutableList

這對我有用。 我們需要將類設為 final,以便它不能被繼承。 將列表保留為最終版本(這只是對列表的引用)。 在構造函數中,使用 unmodifiableList 並創建一個新列表並為其分配一個引用而不是輸入。

public final class ImmutableClassWithList {
    
    private  final List<String> list;
    
    public ImmutableClassWithList(ArrayList<String> input) {
        list = Collections.unmodifiableList(new ArrayList<String>(input));
    }
    
    public List<String> getList(){
        
        return list;
    }

}

由於 Java 10 您可以使用List.copyOf static 方法。

public void setList(List<T> list){
  this.list = List.copyOf(list);
}

根據javadoc ,結果列表是不可變的,對基礎列表的任何更改都不會影響新列表。

另一個解決方案是返回arraylist的副本,而不是像這段代碼那樣返回實際的數組列表

import java.util.ArrayList;
import java.util.List;

public final class ImmutableExample {

    private final List<String> strings = new ArrayList<String>();

    public ImmutableExample() {
        strings.add("strin 1");
        strings.add("string 2");
    }

    public List<String> getStrings() {
        List<String> newStrings = new ArrayList<String>();
        strings.forEach(s -> newStrings.add(s));
        return newStrings;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ImmutableExample im = new ImmutableExample();
        System.out.println(im.getStrings());
        im.getStrings().add("string 3");
        System.out.println(im.getStrings());

    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM