Почему мой ArrayList содержит N копий последнего элемента, добавленного в список?
Я добавляю три разных объекта в ArrayList, но список содержит три копии последнего добавленного мной объекта.
Например:
for (Foo f : list) {
System.out.println(f.getValue());
}
Ожидаемое:
0
1
2
Actual:
2
2
2
Какую ошибку я сделал?
Примечание: это предназначено для канонического Q & A для множества подобных проблем, возникающих на этом сайте.
Ответы
Ответ 1
Эта проблема имеет две типичные причины:
-
Статические поля, используемые объектами, которые вы сохранили в списке
-
Случайно добавив тот же объект в список
Статические поля
Если объекты в вашем списке хранят данные в статических полях, каждый объект в вашем списке будет выглядеть одинаково, поскольку он содержит одинаковые значения. Рассмотрим класс ниже:
public class Foo {
private static int value;
// ^^^^^^------------ - Here 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);
}
Ответ 2
Ваша проблема связана с типом 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
, чтобы проверить достоверность приведенного выше фрагмента; вы можете иметь некоторую другую реализацию на основе вашего варианта использования.
Ответ 3
Были проблемы с календарным экземпляром.
Неправильный код:
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, убедитесь, что вы добавили новый объект и еще не использованный объект. Случается, что при добавлении одной и той же копии объекта этот объект добавляется в разные позиции в ArrayList. И когда вы вносите изменения в один, поскольку одна и та же копия добавляется снова и снова, все копии становятся затронутыми.
Например,
Скажем, у вас есть ArrayList:
ArrayList<Card> list = new ArrayList<Card>();
Card c = new Card();
Теперь, если вы добавите эту карту c в список, она будет добавлена без проблем. Он будет сохранен в месте 0. Но когда вы сохраните ту же Карту c в списке, она будет сохранена в местоположении 1. Так что помните, что вы добавили один и тот же 1 объект в два разных местоположения в списке. Теперь, если вы внесете изменения в объект Card c, объекты в списке в местах 0 и 1 также отражают это изменение, потому что они являются одним и тем же объектом.
Одним из решений было бы сделать конструктор класса 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));