web-dev-qa-db-ja.com

クラス内のプライベートフィールドの変更を防ぐにはどうすればよいですか?

このクラスがあると想像してください:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public String[] getArr() 
  {
    return arr;
  }
}

さて、上記のクラスを使用する別のクラスがあります:

Test test = new Test();
test.getArr()[0] ="some value!"; //!!!

これが問題です。クラスのプライベートフィールドに外部からアクセスしました。どうすればこれを防ぐことができますか?この配列を不変にする方法はありますか?これは、すべてのgetterメソッドを使用して、プライベートフィールドにアクセスするために作業を進められることを意味しますか? (グアバなどのライブラリは必要ありません。これを行う正しい方法を知る必要があるだけです)。

164
Hossein

配列のcopyを返す必要があります。

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}
161
OldCurmudgeon

配列の代わりにリストを使用できる場合、 コレクションは変更不可能なリストを提供します

public List<String> getList() {
    return Collections.unmodifiableList(list);
}
375
sp00m

修飾子privateは、フィールド自体のみを他のクラスからのアクセスから保護しますが、このフィールドによるオブジェクト参照は保護しません。参照されるオブジェクトを保護する必要がある場合は、それを提供しないでください。変化する

public String [] getArr ()
{
    return arr;
}

に:

public String [] getArr ()
{
    return arr.clone ();
}

または

public int getArrLength ()
{
    return arr.length;
}

public String getArrElementAt (int index)
{
    return arr [index];
}
45

_Collections.unmodifiableList_はすでに言及されています-奇妙なことにArrays.asList()は言及されていません!私の解決策は、外部からリストを使用し、次のように配列をラップすることです。

_String[] arr = new String[]{"1", "2"}; 
public List<String> getList() {
    return Collections.unmodifiableList(Arrays.asList(arr));
}
_

配列をコピーする際の問題は、コードにアクセスするたびにそれを実行し、配列が大きい場合、ガベージコレクターに多くの作業を確実に作成することです。したがって、コピーはシンプルですが、本当に悪いアプローチです-私は「安い」と言いますが、メモリが高価です!特に、2つ以上の要素がある場合。

_Arrays.asList_および_Collections.unmodifiableList_のソースコードを見ると、実際にはあまり作成されていません。 1つ目は配列をコピーせずにラップするだけで、2つ目はリストをラップするだけで、変更を利用できません。

28
michael_s

ImmutableList を使用することもできます。これは標準のunmodifiableListよりも優れているはずです。このクラスは、Googleが作成した Guava ライブラリの一部です。

説明は次のとおりです。

Collections.unmodifiableList(Java.util.List)とは異なり、これは引き続き変更可能な個別のコレクションのビューであり、ImmutableListのインスタンスには独自のプライベートデータが含まれており、変更されることはありません

使用方法の簡単な例を次に示します。

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}
6
iTech

この観点では、システムアレイのコピーを使用する必要があります。

public String[] getArr() {
   if (arr != null) {
      String[] arrcpy = new String[arr.length];
      System.arraycopy(arr, 0, arrcpy, 0, arr.length);
      return arrcpy;
   } else
      return null;
   }
}
3
G M Ramesh

データのコピーを返すことができます。データを変更することを選択した呼び出し元は、コピーのみを変更します

public class Test {
    private static String[] arr = new String[] { "1", "2" };

    public String[] getArr() {

        String[] b = new String[arr.length];

        System.arraycopy(arr, 0, b, 0, arr.length);

        return b;
    }
}
2

問題の要点は、可変オブジェクトへのポインターを返すことです。おっとっと。オブジェクトを不変(変更不可能なリストソリューション)にするか、オブジェクトのコピーを返します。

一般的な問題として、オブジェクトの最終性は、オブジェクトが変更可能である場合、オブジェクトが変更されるのを防ぎません。これらの2つの問題は「キスのいとこ」です。

2
ncmathsadist

変更不可能なリストを返すことは良い考えです。ただし、ゲッターメソッドの呼び出し中に変更不可にされたリストは、クラスまたはクラスから派生したクラスによって変更される可能性があります。

代わりに、クラスを拡張するすべての人に、リストを変更してはならないことを明確にする必要があります。

したがって、あなたの例では、次のコードにつながる可能性があります。

import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;

public class Test {
    public static final List<String> STRINGS =
        Collections.unmodifiableList(
            Arrays.asList("1", "2"));

    public final List<String> getStrings() {
        return STRINGS;
    }
}

上記の例では、STRINGSフィールドをパブリックにしていますが、値はすでにわかっているため、原則としてメソッド呼び出しを廃止できます。

また、クラスインスタンスの構築中に変更不可能になったprivate final List<String>フィールドに文字列を割り当てることもできます。 (コンストラクターの)定数またはインスタンス化引数の使用は、クラスの設計に依存します。

import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;

public class Test {
    private final List<String> strings;

    public Test(final String ... strings) {
        this.strings = Collections.unmodifiableList(Arrays
                .asList(strings));
    }

    public final List<String> getStrings() {
        return strings;
    }
}
1
Maarten Bodewes

はい、配列のコピーを返す必要があります。

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
0
kofemann