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


165

私がこのクラスを持っていると想像してください:

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

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

今、私は上記のクラスを使用する別のクラスがあります:

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

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


10
実際には、フィールドを変更することfinalできません。ただし、Objectフィールドによる参照の変更を防ぐのはより複雑です。
OldCurmudgeon 2013

22
プライベートフィールドに参照が格納されている配列を変更できることは、プライベートフィールドを変更できることと同じであると考えると、メンタルモデルに問題があります。
ジョーレン2013

45
非公開の場合、なぜそれを最初に公開するのですか?
Erik Reppen 2013

7
これを理解するのに少し時間がかかりましたが、すべてのデータ構造がオブジェクト内に完全にカプセル化されていることを確認すると、常にコードが改善されます。つまり、「arr」を「取得」する方法はなく、内部で実行する必要があるかどうかを確認します。クラスまたはイテレータを提供します。
Bill K

8
この質問がなぜそれほど注目されたのか他の誰かも驚いていますか?
ローマ

回答:


163

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

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

121
これは質問に対する正解に投票されましたが、問題に対する最良の解決策は実際にはsp00mが言うように-を返すことUnmodifiable Listです。
OldCurmudgeon 2013

48
実際に配列を返す必要がある場合はそうではありません。
2013

8
実際、議論された問題に対する最善の解決策は、@ MikhailVladimirovが言うように、しばしばあります:- 配列またはコレクションのビューを提供または返すこと。
ChristofferHammarström2013

20
ただし、データの浅いコピーではなく、深いコピーであることを確認してください。文字列の場合、違いはありません。Dateのように、インスタンスのコンテンツを変更できる他のクラスでは、違いが生じる可能性があります。
2013

16
@Svish私はもっと抜本的だったはずです:API関数から配列を返す場合、それは間違っています。ライブラリ内のプライベート関数ではそれ正しいかもしれません。API(変更可能性に対する保護が役割を果たす)では、決してそうではありません
コンラートルドルフ

377

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

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

Collections.unmodifiableList(list)フィールドへの割り当てとして使用する回答追加し、クラス自体によってリストが変更されないようにしました。
Maarten Bodewes 2014

この方法を逆コンパイルすると、たくさんの新しいキーワードができます。これは、getList()が多くのアクセスを取得するときにパフォーマンスを低下させません。たとえば、レンダリングコードで。
Thomas15v 14

2
これは、配列の要素自体が不変であるString場合に機能しますが、それらが可変である場合は、呼び出し元がそれらを変更できないようにするために何かを行う必要がある場合があります。
Lii 2015

45

修飾子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];
}

5
この記事では、サブクラス化できるオブジェクトに対してのみ、配列に対してクローンを使用することについて反対していません。
リンヘッドリー2013

28

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つ目はリストをラップするだけなので、変更を加えることができません。


ここでは、文字列を含む配列が毎回コピーされると想定しています。そうではなく、不変文字列への参照のみがコピーされます。配列が巨大でない限り、オーバーヘッドは無視できます。配列が巨大な場合は、リストなどimmutableListすべてにアクセスしてください。
Maarten Bodewes 2014

いいえ、私はその仮定をしませんでした-どこ?それはコピーされる参照についてです-それが文字列または他のオブジェクトであるかどうかは関係ありません。大きな配列の場合、配列をコピーすることはお勧めしません。大きな配列の場合、Arrays.asListand Collections.unmodifiableList操作のほうがおそらく安価です。誰かが必要な要素の数を計算できるかもしれませんが、変更を防ぐためだけにコピーされる何千もの要素を含む配列を持つforループのコードを見たことがあるでしょう-それはおかしいです!
michael_s 2014

6

また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);
  }
}

返されるリストを変更せずにクラスを信頼できないTest場合は、このソリューションを使用してください。それImmutableListが返され、リストの要素も不変であることを確認する必要があります。そうでなければ、私の解決策も安全でなければなりません。
Maarten Bodewes 2014

3

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

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

-1 cloneはの呼び出しに対して何も実行せず、簡潔ではありません。
Maarten Bodewes 2014

2

データのコピーを返すことができます。データを変更することを選択した発信者は、コピーのみを変更します

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

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

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


Javaには参照がありますが、Cにはポインタがあります。十分に言った。さらに、「最終」修飾子は、それが適用される場所に応じて、複数の影響を与える可能性があります。クラスメンバーに適用すると、 "="(代入)演算子を使用してメンバーに値を1回だけ割り当てることが強制されます。これは不変性とは関係ありません。メンバーの参照を新しいもの(同じクラスの他のインスタンス)に置き換えると、以前のインスタンスは変更されません(ただし、他の参照がそれらを保持していない場合はGCされる可能性があります)。
gyorgyabraham 2013

1

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

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

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

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

0

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

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.