Por que minha ArrayList contém N cópias do último item adicionado à lista?

Estou adicionando três objects diferentes a uma ArrayList, mas a lista contém três cópias do último object que adicionei.

Por exemplo:

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

Esperado:

 0 1 2 

Real:

 2 2 2 

Que erro cometi?

Nota: isto é projetado para ser um Q & A canônico para os numerosos problemas semelhantes que surgem neste site.

Esse problema tem duas causas típicas:

  • Campos estáticos usados ​​pelos objects que você armazenou na lista

  • Acidentalmente adicionando o mesmo object à lista

Campos estáticos

Se os objects em sua lista armazenam dados em campos estáticos, cada object da lista parecerá o mesmo, pois eles contêm os mesmos valores. Considere a class abaixo:

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

Nesse exemplo, há apenas um int value que é compartilhado entre todas as instâncias do Foo porque é declarado static . (Veja o tutorial “Entendendo os Membros da Classe” ).

Se você adicionar vários objects Foo a uma lista usando o código abaixo, cada instância retornará 3 de uma chamada para getValue() :

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

A solução é simples - não use palavras-chave static para campos em sua class, a menos que você realmente queira que os valores sejam compartilhados entre todas as instâncias dessa class.

Adicionando o mesmo object

Se você adicionar uma variável temporária a uma lista, deverá criar uma nova instância toda vez que fizer um loop. Considere o seguinte trecho de código incorreto:

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

Aqui, o object tmp foi construído fora do loop. Como resultado, a mesma instância de object está sendo adicionada à lista três vezes. A instância manterá o valor 2 , porque esse foi o valor passado durante a última chamada para setValue() .

Para corrigir isso, basta mover a construção do object dentro do loop:

 List list = new ArrayList(); for (int i = 0; i < 3; i++) { Foo tmp = new Foo(); // <-- fresh instance! tmp.setValue(i); list.add(tmp); } 

Seu problema é com o tipo static que requer uma nova boot toda vez que um loop é iterado. Se você estiver em um loop, é melhor manter a boot concreta dentro do loop.

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

Ao invés de:

 List 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 } 

Aqui tag é uma variável em SomeStaticClass para verificar a validade do snippet acima; você pode ter alguma outra implementação baseada no seu caso de uso.

Toda vez que você adicionar um object a um ArrayList, certifique-se de adicionar um novo object e não o object já usado. O que está acontecendo é que, quando você adiciona a mesma cópia de um object, esse mesmo object é adicionado a posições diferentes em uma ArrayList. E quando você faz alterações em um, porque a mesma cópia é adicionada repetidas vezes, todas as cópias são afetadas. Por exemplo, digamos que você tenha uma ArrayList assim:

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

Agora, se você adicionar este cartão c à lista, ele será adicionado sem problemas. Ele será salvo no local 0. Mas, quando você salvar o mesmo Card c na lista, ele será salvo no local 1. Portanto, lembre-se de que você adicionou o mesmo 1 object a dois locais diferentes em uma lista. Agora, se você fizer uma alteração no object Card c, os objects em uma lista no local 0 e 1 também refletirão essa alteração, porque eles são o mesmo object.

Uma solução seria criar um construtor na class Card, que aceita outro object Card. Então, nesse construtor, você pode definir as propriedades assim:

 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 } 

E digamos que você tenha a mesma cópia do cartão, então, no momento de adicionar um novo object, você pode fazer isso:

 list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf)); 

Tive o mesmo problema com a instância do calendar.

Código errado:

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

Você tem que criar um novo object do calendar, o que pode ser feito com 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); }