ArrayListに3つの異なるオブジェクトを追加していますが、リストには最後に追加したオブジェクトの3つのコピーが含まれています。
例えば:
for (Foo f : list) {
System.out.println(f.getValue());
}
期待される:
0
1
2
実際:
2
2
2
どのような間違いを犯しましたか?
注:これは、このサイトで発生する多数の同様の問題に対する標準的なQ&Aになるように設計されています。
この問題には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);
}
_
あなたの問題は、ループが繰り返されるたびに新しい初期化を必要とするタイプ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
の変数です。ユースケースに基づいて他の実装を行うことができます。
カレンダーインスタンスでも同じ問題が発生しました。
不正なコード:
_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);
}
_
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));