Почему мой 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)на последней итерации цикла.
Дункан Джонс
1
Хорошо, так что здесь проблема из-за одного и того же объекта, так ли, что, если я добавляю один и тот же объект три раза в список массивов, все три местоположения arrayylist obj будут ссылаться на тот же объект?
Dev
1
@ Дэв Ага, именно так.
Дункан Джонс
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переменная, SomeStaticClassчтобы проверить правильность приведенного выше фрагмента; у вас может быть другая реализация в зависимости от вашего варианта использования.

Шашанк
источник
Что вы имеете в виду под «типом static»? Что для вас будет нестатическим классом?
4castle, 03
например нестатический: public class SomeClass{/*some code*/} & статический: public static class SomeStaticClass{/*some code*/}. Надеюсь, теперь стало понятнее.
Shashank
1
Поскольку все объекты статического класса имеют один и тот же адрес, если они инициализируются в цикле и устанавливаются с разными значениями на каждой итерации. Все они в конечном итоге будут иметь одинаковое значение, которое будет равно значению последней итерации или времени, когда последний объект был изменен. Надеюсь, теперь стало понятнее.
Shashank
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);

}
басти12354
источник
4

Каждый раз, когда вы добавляете объект в ArrayList, убедитесь, что вы добавляете новый объект, а не уже используемый объект. Происходит то, что когда вы добавляете одну и ту же копию объекта, этот же объект добавляется в разные позиции в 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
}

Допустим, у вас есть одна и та же копия Card, поэтому во время добавления нового объекта вы можете сделать это:

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);
} 
Шейх Мохиб
источник