Dlaczego moja ArrayList zawiera N kopii ostatniego elementu dodanego do listy?

Dodaję trzy różne obiekty do ArrayList, ale lista zawiera trzy kopie ostatniego dodanego obiektu.

Na przykład:

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

Oczekiwano:

0
1
2

Rzeczywista:

2
2
2
Jaki błąd popełniłem?

Uwaga: ma to być kanoniczne pytanie i odpowiedź na wiele podobnych problemów, które pojawiają się na tej stronie.

Author: Duncan Jones, 2013-11-07

4 answers

Ten problem ma dwie typowe przyczyny:

  • Pola statyczne używane przez obiekty przechowywane na liście

  • Przypadkowe dodanie tego samego obiektu do listy

Pola Statyczne

Jeśli obiekty na liście przechowują dane w polach statycznych, każdy obiekt na liście będzie wyglądał tak samo, ponieważ zawiera te same wartości. Rozważmy klasę poniżej:

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

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

  public int getValue() {
    return value;
  }
}

W tym przykładzie jest tylko jeden int value która jest współdzielona pomiędzy wszystkimi instancjami Foo, ponieważ jest zadeklarowana static. (Patrz "zrozumienie członków klasy" tutorial.)

Jeśli dodasz wiele obiektów Foo do listy za pomocą poniższego kodu, każda instancja zwróci 3 z wywołania do getValue():

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

Rozwiązanie jest proste-nie używaj słów kluczowych static dla pól w swojej klasie, chyba że chcesz, aby wartości były współdzielone pomiędzy każdą instancją tej klasy.

Dodanie tego samego Obiekt

Jeśli dodasz zmienną tymczasową do listy, musisz utworzyć nową instancję za każdym razem, gdy robisz pętlę. Rozważmy następujący błędny fragment kodu:

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

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

Tutaj, tmp obiekt został zbudowany poza pętlą. W rezultacie ta sama instancja obiektu jest dodawana do listy trzy razy. Instancja będzie posiadać wartość 2, ponieważ była to wartość przekazana podczas ostatniego wywołania do setValue().

Aby to naprawić, po prostu przesuń konstrukcję obiektu wewnątrz pętli:

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);
}
 111
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-17 08:06:47

Twój problem polega na typie static, który wymaga nowej inicjalizacji za każdym razem, gdy pętla jest iteracyjna. Jeśli jesteś w pętli, lepiej jest zachować konkretną inicjalizację wewnątrz pętli.

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

Zamiast:

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
}

Tutaj tag jest zmienna w SomeStaticClass, aby sprawdzić poprawność powyższego fragmentu; możesz mieć inną implementację opartą na Twoim przypadku użycia.

 5
Author: Shashank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-03 03:52:41

Miał ten sam problem z instancją kalendarza.

Zły kod:

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

Musisz utworzyć nowy obiekt kalendarza, co można zrobić za pomocą 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);

}
 3
Author: basti12354,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-22 19:27:29

Za każdym razem, gdy dodajesz obiekt do ArrayList, upewnij się, że dodajesz nowy obiekt, a nie jeszcze używany obiekt. Dzieje się tak, że po dodaniu tej samej 1 kopii obiektu ten sam obiekt jest dodawany do różnych pozycji w tablicy. A gdy zmienisz na jeden, ponieważ ta sama kopia jest dodawana w kółko, wszystkie kopie zostaną naruszone. Na przykład, Powiedzmy, że masz taką Arraylistę:

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

Teraz, jeśli dodasz tę kartę c do listy, zostanie dodana bez problemu. Informatyka zostaną zapisane w miejscu 0. Ale po zapisaniu tej samej karty c na liście, zostanie ona zapisana w miejscu 1. Pamiętaj więc, że dodałeś ten sam obiekt 1 do dwóch różnych lokalizacji na liście. Teraz, jeśli zmienisz ten obiekt karty c, obiekty na liście w lokalizacji 0 i 1 również odzwierciedlą tę zmianę, ponieważ są tym samym obiektem.

Jednym z rozwiązań byłoby stworzenie konstruktora w klasie kart, który akceptuje inny obiekt karty. Następnie w tym konstruktorze możesz ustawić właściwości tak:

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
}

I powiedzmy, że masz tę samą 1 kopię karty, więc w momencie dodawania nowego obiektu możesz to zrobić:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));
 2
Author: Desert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-27 06:32:00