なぜ私のArrayListには、リストに追加された最後の項目のN個のコピーが含まれているのでしょうか?

java list arraylist static


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

例えば

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

Expected:

0
1
2

Actual:

2
2
2

どんなミスをしてしまったのだろうか。

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





Answer 1 Duncan Jones


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

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

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

静的なフィールド

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

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

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

  public int getValue() {
    return value;
  }
}

その例では、 static と宣言されているため、 Foo のすべてのインスタンス間で共有される 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回追加されます。インスタンスは、値 2 を保持します。これは、 setValue() への最後の呼び出し中に渡された値だからです。

これを修正するには、オブジェクトの構造をループの内側に移動させればよいのです。

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



Answer 2 Shashank


あなたの問題は、ループが繰り返されるたびに新しい初期化を必要とする 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 の変数です。ユースケースに基づいて他の実装を行うことができます。




Answer 3 basti12354


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

コードが違う

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

}



Answer 4 Faraz


ArrayListにオブジェクトを追加する際には、必ず新しいオブジェクトを追加し、既に使用しているオブジェクトを追加しないようにしてください。何が起こっているかというと、同じコピーを1つ追加すると、同じオブジェクトがArrayList内の異なる位置に追加されてしまいます。そして、同じコピーが何度も何度も追加されるので、1つに変更を加えると、すべてのコピーが影響を受けてしまいます。例えば、以下のようなArrayListを持っているとします。

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

これで,このカードをリストに追加すると,問題なく追加されます.しかし、同じカードcをリストに保存すると、1の場所に保存されます。このように、同じ1のオブジェクトをリストの2つの異なる場所に追加したことを覚えておいてください。これで、カードオブジェクトcに変更を加えた場合、リストの0と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枚のCardを持っているとすると、新しいオブジェクトを追加するときに、このようにすることができます。

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));