Чому мій ArrayList містить N копій останнього елемента, доданого до списку?


84

Я додаю три різні об’єкти до ArrayList, але список містить три копії останнього доданого мною об’єкта.

Наприклад:

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

Очікується:

0
1
2

Фактично:

2
2
2

Яку помилку я зробив?

Примітка: це розроблено для канонічних запитань та відповідей щодо численних подібних проблем, що виникають на цьому веб-сайті.

Відповіді:


152

Ця проблема має дві типові причини:

  • Статичні поля, що використовуються об'єктами, які ви зберігаєте у списку

  • Випадково додавши той самий об’єкт до списку

Статичні поля

Якщо об’єкти у вашому списку зберігають дані у статичних полях, кожен об’єкт у вашому списку виявиться однаковим, оскільки вони містять однакові значення. Розглянемо клас нижче:

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

У цьому прикладі є лише один, int valueякий ділиться між усіма екземплярами, Fooоскільки він оголошений static. (Див . Підручник "Розуміння учасників класу" ).

Якщо ви додасте кілька Fooоб’єктів до списку за допомогою коду нижче, кожен екземпляр повернеться 3із виклику до getValue():

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об’єкт був побудований поза циклом. Як результат, той самий екземпляр об’єкта тричі додається до списку. Екземпляр зберігатиме значення 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);
}

1
привіт @Duncan приємне рішення, я хочу запитати вас, що в розділі "Додавання одного і того ж об'єкта", чому три різні екземпляри будуть містити значення 2, чи не всі три екземпляри повинні містити 3 різні значення? сподіваюся, ви скоро відповісте, спасибі
Dev

3
@Dev Оскільки той самий об’єкт ( tmp) тричі додається до списку. І цей об’єкт має значення два через виклик до tmp.setValue(2)в кінцевій ітерації циклу.
Duncan Jones

1
добре, отже, проблема полягає в тому, що один і той же об'єкт. Чи так, якщо я додаю той самий об'єкт три рази в список масивів, усі три розташування об'єкту arraylist будуть посилатися на один і той же об'єкт?
Dev

1
@Dev Yup, саме це.
Дункан Джонс,

1
Очевидний факт, який я спочатку пропустив: стосовно частини you must create a new instance each time you loopв розділі Adding the same object: Зверніть увагу, що посилання, про яке йдеться, стосується об’єкта, який ви додаєте, а НЕ об’єкта, до якого ви його додаєте.
на

8

Ваша проблема полягає в типі, 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змінна in SomeStaticClassдля перевірки дійсності наведеного фрагмента; Ви можете застосувати іншу реалізацію на основі вашого випадку використання.


Що ви маєте на увазі під "типом static"? Що для вас буде нестатичним класом?
4castle

наприклад нестатичних: public class SomeClass{/*some code*/} & статичний: public static class SomeStaticClass{/*some code*/}. Сподіваюся, зараз це зрозуміліше.
Шашанк

1
Оскільки всі об'єкти статичного класу мають однакову адресу, якщо вони ініціалізовані у циклі та встановлюються з різними значеннями в кожній ітерації. В кінцевому підсумку всі вони матимуть ідентичне значення, яке дорівнюватиме значенню останньої ітерації або коли був змінений останній об’єкт. Сподіваюся, зараз це зрозуміліше.
Шашанк

6

Виникли ті самі проблеми з екземпляром календаря.

Невірний код:

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

Кожного разу, коли ви додаєте об’єкт до ArrayList, переконайтесь, що ви додаєте новий об’єкт, а не вже використаний об’єкт. Що відбувається, це те, що коли ви додаєте ту саму 1 копію об’єкта, той самий об’єкт додається до різних позицій у ArrayList. І коли ви вносите зміни в одну, оскільки одна і та ж копія додається знову і знову, всі копії зазнають впливу. Наприклад, скажімо, що у вас є ArrayList, такий:

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

Тепер, якщо ви додасте цю картку c до списку, вона не буде додана без проблем. Він буде збережений у місці 0. Але, коли ви збережете ту саму Картку c у списку, вона буде збережена в місці 1. Тож пам’ятайте, що ви додали той самий 1 об’єкт до двох різних місць у списку. Тепер, якщо ви внесете зміни в об'єкт Card 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 копія картки, тож під час додавання нового об’єкта ви можете зробити це:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

2

Це також може спричинити використання того самого посилання замість використання нового.

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

 setdata();
......

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

Замість:

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

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.