Warum enthält meine ArrayList N Kopien des letzten zur Liste hinzugefügten Elements?

84

Ich füge einer ArrayList drei verschiedene Objekte hinzu, aber die Liste enthält drei Kopien des zuletzt hinzugefügten Objekts.

Zum Beispiel:

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

Erwartet:

0
1
2

Tatsächlich:

2
2
2

Welchen Fehler habe ich gemacht?

Hinweis: Dies ist eine kanonische Frage und Antwort für die zahlreichen ähnlichen Probleme, die auf dieser Website auftreten.

Duncan Jones
quelle

Antworten:

152

Dieses Problem hat zwei typische Ursachen:

  • Statische Felder, die von den Objekten verwendet werden, die Sie in der Liste gespeichert haben

  • Versehentlich dasselbe Objekt zur Liste hinzufügen

Statische Felder

Wenn die Objekte in Ihrer Liste Daten in statischen Feldern speichern, scheint jedes Objekt in Ihrer Liste dasselbe zu sein, da sie dieselben Werte enthalten. Betrachten Sie die folgende Klasse:

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!
  
  public Foo(int value) {
    this.value = value;
  }
  
  public int getValue() {
    return value;
  }
}

In diesem Beispiel gibt es nur eine, int valuedie von allen Instanzen gemeinsam genutzt wird, Fooweil sie deklariert ist static. (Siehe Tutorial "Grundlegendes zu Klassenmitgliedern" .)

Wenn Sie Fooeiner Liste mit dem folgenden Code mehrere Objekte hinzufügen , kehrt jede Instanz 3von einem Aufruf an Folgendes zurück getValue():

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

Die Lösung ist einfach: Verwenden Sie die staticSchlüsselwörter nicht für Felder in Ihrer Klasse, es sei denn, Sie möchten tatsächlich, dass die Werte von jeder Instanz dieser Klasse gemeinsam genutzt werden.

Hinzufügen des gleichen Objekts

Wenn Sie einer Liste eine temporäre Variable hinzufügen, müssen Sie bei jeder Schleife eine neue Instanz des hinzuzufügenden Objekts erstellen. Betrachten Sie das folgende fehlerhafte Code-Snippet:

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

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

Hier wurde das tmpObjekt außerhalb der Schleife konstruiert. Infolgedessen wird dieselbe Objektinstanz dreimal zur Liste hinzugefügt. Die Instanz enthält den Wert 2, da dies der Wert war, der beim letzten Aufruf von übergeben wurde setValue().

Um dies zu beheben, verschieben Sie einfach die Objektkonstruktion innerhalb der Schleife:

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);
}
Duncan Jones
quelle
1
Hallo @Duncan nette Lösung, ich möchte Sie fragen, warum in "Hinzufügen des gleichen Objekts", warum drei verschiedene Instanzen den Wert 2 enthalten, nicht alle drei Instanzen 3 verschiedene Werte enthalten sollten? Ich hoffe, Sie werden bald antworten, danke
Dev
3
@Dev Da dasselbe Objekt ( tmp) dreimal zur Liste hinzugefügt wird. Und dieses Objekt hat aufgrund des Aufrufs tmp.setValue(2)in der letzten Iteration der Schleife den Wert zwei .
Duncan Jones
1
ok also hier liegt das Problem an demselben Objekt. Ist es so, wenn ich dasselbe Objekt dreimal in eine Array-Liste einfüge, beziehen sich alle drei Positionen von arraylist obj auf dasselbe Objekt?
Dev
1
@ Dev Yup, genau das ist es.
Duncan Jones
1
Eine offensichtliche Tatsache, die ich anfangs übersehen habe: bezüglich des Teils you must create a new instance each time you loopim Abschnitt Adding the same object: Bitte beachten Sie, dass die Instanz, auf die Bezug genommen wird, das Objekt betrifft, das Sie hinzufügen, NICHT das Objekt, zu dem Sie es hinzufügen.
am
8

Ihr Problem liegt in dem Typ, staticder bei jeder Iteration einer Schleife eine neue Initialisierung erfordert. Wenn Sie sich in einer Schleife befinden, ist es besser, die konkrete Initialisierung in der Schleife zu belassen.

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

Anstatt von:

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
}

Hier tagist eine Variable SomeStaticClass, um die Gültigkeit des obigen Snippets zu überprüfen. Sie können je nach Anwendungsfall eine andere Implementierung durchführen.

Shashank
quelle
Was meinst du mit "Typ static"? Was wäre für Sie eine nicht statische Klasse?
4castle
zB nicht statisch: public class SomeClass{/*some code*/} & statisch : public static class SomeStaticClass{/*some code*/}. Ich hoffe es ist jetzt klarer.
Shashank
1
Da alle Objekte einer statischen Klasse dieselbe Adresse haben, wenn sie in einer Schleife initialisiert und in jeder Iteration mit unterschiedlichen Werten festgelegt werden. Alle haben einen identischen Wert, der dem Wert der letzten Iteration entspricht oder wenn das letzte Objekt geändert wurde. Ich hoffe es ist jetzt klarer.
Shashank
6

Hatte das gleiche Problem mit der Kalenderinstanz.

Falscher Code:

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

Sie müssen ein NEUES Objekt des Kalenders erstellen, was möglich ist 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);

}
basti12354
quelle
4

Stellen Sie bei jedem Hinzufügen eines Objekts zu einer ArrayList sicher, dass Sie ein neues Objekt und ein noch nicht verwendetes Objekt hinzufügen. Wenn Sie dieselbe 1 Kopie des Objekts hinzufügen, wird dasselbe Objekt an verschiedenen Positionen in einer ArrayList hinzugefügt. Und wenn Sie eine Änderung vornehmen, werden alle Kopien betroffen, da immer wieder dieselbe Kopie hinzugefügt wird. Angenommen, Sie haben eine ArrayList wie folgt:

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

Wenn Sie nun diese Karte c zur Liste hinzufügen, wird sie problemlos hinzugefügt. Es wird an Position 0 gespeichert. Wenn Sie jedoch dieselbe Karte c in der Liste speichern, wird sie an Position 1 gespeichert. Denken Sie also daran, dass Sie dasselbe 1 Objekt an zwei verschiedenen Positionen in einer Liste hinzugefügt haben. Wenn Sie nun das Kartenobjekt c ändern, spiegeln die Objekte in einer Liste an den Positionen 0 und 1 diese Änderung ebenfalls wider, da sie dasselbe Objekt sind.

Eine Lösung wäre, einen Konstruktor in der Kartenklasse zu erstellen, der ein anderes Kartenobjekt akzeptiert. Dann können Sie in diesem Konstruktor die Eigenschaften wie folgt festlegen:

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
}

Angenommen, Sie haben dieselbe 1 Kopie der Karte. Wenn Sie also ein neues Objekt hinzufügen, können Sie Folgendes tun:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));
Faraz
quelle
2

Dies kann auch dazu führen, dass dieselbe Referenz anstelle einer neuen verwendet wird.

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

 setdata();
......

public void setdata(int i) {
  Foo temp = new Foo();
  tmp.setValue(i);
  list.add(tmp);
}

Anstatt von:

List<Foo> list = new ArrayList<Foo>(); 
Foo temp = new Foo();       
setdata();
......

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
Shaikh Mohib
quelle