web-dev-qa-db-ja.com

ArrayListにリストに追加された最後のアイテムのN個のコピーが含まれているのはなぜですか?

ArrayListに3つの異なるオブジェクトを追加していますが、リストには最後に追加したオブジェクトの3つのコピーが含まれています。

例えば:

for (Foo f : list) {
  System.out.println(f.getValue());
}    

期待される:

0
1
2

実際:

2
2
2

どのような間違いを犯しましたか?

注:これは、このサイトで発生する多数の同様の問題に対する標準的なQ&Aになるように設計されています。

71
Duncan Jones

この問題には2つの典型的な原因があります。

  • リストに保存したオブジェクトが使用する静的フィールド

  • 誤ってsameオブジェクトをリストに追加する

静的フィールド

リスト内のオブジェクトが静的フィールドにデータを格納している場合、リスト内の各オブジェクトは同じ値を保持しているため、同じように見えます。以下のクラスを検討してください。

_public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!

  public Foo(int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }
}
_

その例では、Fooとして宣言されているため、staticのすべてのインスタンス間で共有される_int value_は1つだけです。 ( 「クラスメンバについて」 チュートリアルを参照してください。)

以下のコードを使用して複数のFooオブジェクトをリストに追加すると、各インスタンスはgetValue()の呼び出しから_3_を返します。

_for (int i = 0; i < 4; i++) {      
  list.add(new Foo(i));
}
_

解決策は簡単です。クラスのすべてのインスタンス間で値を共有したい場合を除いて、クラスのフィールドにstaticキーワードを使用しないでください。

同じオブジェクトを追加する

リストに一時変数を追加する場合、ループするたびに、追加するオブジェクトの新しいインスタンスを作成する必要があります。次の誤ったコードスニペットを考慮してください。

_List<Foo> list = new ArrayList<Foo>();    
Foo tmp = new Foo();

for (int i = 0; i < 3; i++) {
  tmp.setValue(i);
  list.add(tmp);
}
_

ここで、tmpオブジェクトはループ外で構築されました。その結果、同じオブジェクトインスタンスがリストに3回追加されています。インスタンスはsetValue()への最後の呼び出し中に渡された値だったため、インスタンスは値_2_を保持します。

これを修正するには、ループ内でオブジェクト構造を移動するだけです。

_List<Foo> list = new ArrayList<Foo>();        

for (int i = 0; i < 3; i++) {
  Foo tmp = new Foo(); // <-- fresh instance!
  tmp.setValue(i);
  list.add(tmp);
}
_
125
Duncan Jones

あなたの問題は、ループが繰り返されるたびに新しい初期化を必要とするタイプstaticにあります。ループ内にいる場合は、ループ内で具体的な初期化を維持することをお勧めします。

List<Object> objects = new ArrayList<>(); 

for (int i = 0; i < length_you_want; i++) {
    SomeStaticClass myStaticObject = new SomeStaticClass();
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
}

の代わりに:

List<Object> objects = new ArrayList<>(); 

SomeStaticClass myStaticObject = new SomeStaticClass();
for (int i = 0; i < length; i++) {
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
    // This will duplicate the last item "length" times
}

ここで、tagは、上記のスニペットの有効性をチェックするSomeStaticClassの変数です。ユースケースに基づいて他の実装を行うことができます。

6
Shashank

カレンダーインスタンスでも同じ問題が発生しました。

不正なコード:

_Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // In the next line lies the error
    Calendar newCal = myCalendar;
    calendarList.add(newCal);
}
_

カレンダーの新しいオブジェクトを作成する必要があります。これは、calendar.clone();で実行できます。

_Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // RIGHT WAY
    Calendar newCal = (Calendar) myCalendar.clone();
    calendarList.add(newCal);

}
_
4
basti12354

ArrayListにオブジェクトを追加するたびに、新しいオブジェクトを追加し、まだ使用されていないオブジェクトを追加してください。何が起こっているかというと、オブジェクトの同じ1つのコピーを追加すると、その同じオブジェクトがArrayListの異なる位置に追加されます。また、1つに変更を加えると、同じコピーが何度も追加されるため、すべてのコピーが影響を受けます。たとえば、次のようなArrayListがあるとします。

ArrayList<Card> list = new ArrayList<Card>();
Card c = new Card();

このカードcをリストに追加しても、問題なく追加されます。場所0に保存されます。ただし、リストに同じカードcを保存すると、場所1に保存されます。したがって、リストの2つの異なる場所に同じ1つのオブジェクトを追加したことに注意してください。カードオブジェクトcを変更すると、位置0および1のリスト内のオブジェクトも同じオブジェクトであるため、その変更を反映します。

1つの解決策は、別のCardオブジェクトを受け入れるCardクラスのコンストラクターを作成することです。次に、そのコンストラクターで、次のようにプロパティを設定できます。

public Card(Card c){
this.property1 = c.getProperty1();
this.property2 = c.getProperty2(); 
... //add all the properties that you have in this class Card this way
}

また、同じカードのコピーが1つあるとしましょう。したがって、新しいオブジェクトを追加するときに、次のことができます。

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));
3
Faraz Durrani