web-dev-qa-db-ja.com

ArrayListメンバー変数を持つ不変オブジェクト-なぜこの変数を変更できるのですか?

さまざまなメンバー変数を持つ1つのクラスがあります。コンストラクターがあり、getterメソッドがありますが、setterメソッドはありません。実際、このオブジェクトは不変でなければなりません。

_public class Example {
   private ArrayList<String> list; 
}
_

さて、次のことに気づきました。getterメソッドで変数リストを取得すると、新しい値を追加することができます-ArrayListを変更できます。この変数に対して次回get()を呼び出すと、変更されたArrayListが返されます。どうすればいいの?二度と設定しなかったので、作業しました! Stringでは、この動作は不可能です。ここでの違いは何ですか?

20
nano7

参照が不変だからといって、リストそれが参照するが不変であるとは限りません。

listが作成された場合でも、finalは許可されます

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

しかし、これはnotを許可します:

// 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);
    }
}

(これにより、リストの変更不可能なビューが作成されることに注意してください。誰かが元の参照を保持している場合でも、リストを使用してリストを変更できます。)


文字列では、この動作は不可能です。では、ここの違いは何ですか?

これは、リストをunmodifiableListにした場合と同様に、Stringはすでに不変(変更不可)であるためです。

比較:

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

listへの参照を返しています。また、listは不変ではありません。


リストを変更したくない場合は、そのコピーを返します。

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

または 変更不可能なリスト を返すことができます:

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

重要なのは、stringを変更していないことを理解することです-どの文字列がリストを参照しているかを変更していますcontains

別の言い方をすれば、もし私があなたの財布から1ドルを取り出し、それを10セント硬貨に交換した場合、1ドルも10セントも変更していません-私はあなたの財布の内容を変更しました。

リストに読み取り専用のビューが必要な場合は、Collections.unmodifiableListwrappingであるリストが変更されるのを防ぐことはできませんが、変更不可能なリストへの参照しか持たないユーザーは、内容。

truly不変リストについては、 GuavaImmutableList クラスを参照してください。

3
Jon Skeet

他の答えが言うように、ゲッターから返すオブジェクトはまだ変更可能です。

コレクションクラスを使用して装飾することで、リストを不変にすることができます。

 list = Collections.unmodifiableList(list);

これをクライアントに返すと、クライアントは要素を追加または削除できなくなります。ただし、リストから要素を取得することはできます。そのため、要素が不変であることを確認する必要があります。

2
planetjones

Collections.unmodifiableList()は、リストを変更不可能にします。これにより、新しい最終的な配列リストが再び作成され、追加、削除、追加、およびクリアのメソッドがオーバーライドされて、サポートされない操作の例外がスローされます。コレクションクラスのハックです。しかし、コンパイル時に、要素の追加や削除を妨げるものではありません。私はむしろそのリストのクローンを作りたいと思います。これは、既存のオブジェクトを不変に保つのに役立ち、新しいリストの作成にコストがかかりません。複製と新しい演算子の違いを読んでください( http://www.javatpoint.com/object-cloning )。また、実行時にコードがクラッシュするのを防ぎます。

2
Pratik Adhau

本当に不変のリストを取得するには、リストの内容の詳細なコピーを作成する必要があります。 UnmodifiableListは、参照のリストをいくぶん不変にするだけです。リストまたは配列の深いコピーを作成することは、サイズが大きくなるにつれてメモリ上で困難になります。シリアライゼーション/デシリアライゼーションを利用して、配列/リストの深いコピーを一時ファイルに保存できます。メンバー変数は不変である必要があるため、セッターは使用できません。ゲッターは、メンバー変数をファイルにシリアル化してから、それを非シリアル化してディープコピーを取得します。シリアライゼーションには、オブジェクトツリーの奥深くに行くという生来の性質があります。これにより、パフォーマンスコストをかけた場合でも完全な不変性が保証されます。

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;
    }
}
0
Rajeev Ranjan

したがって、リストが変更されないように保護する場合は、リストのゲッターメソッドを提供しないでください。

セッターがなかったので、オブジェクトはまだ元のままです。しかし、あなたがすることはそれに新しい/異なるオブジェクトを削除/追加することであり、これは問題ありません。

0
Boro

リスト参照は不変ですが、リストではありません。リスト自体を不変にしたい場合は、 ImmutableList の使用を検討してください

0
simon