¿Por qué mi ArrayList contiene N copias del último elemento agregado a la lista?

Estoy agregando tres objetos diferentes a una ArrayList, pero la lista contiene tres copias del último objeto que agregué.

Por ejemplo:

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

Esperado:

 0 1 2 

Real:

 2 2 2 

¿Qué error he cometido?

Nota: esto está diseñado para ser una sesión de preguntas y respuestas canónicas para los numerosos problemas similares que surgen en este sitio.

Este problema tiene dos causas típicas:

  • Campos estáticos utilizados por los objetos almacenados en la lista

  • Agregar accidentalmente el mismo objeto a la lista

Campos estáticos

Si los objetos en su lista almacenan datos en campos estáticos, cada objeto en su lista parecerá ser el mismo porque tienen los mismos valores. Considera la clase a continuación:

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

En ese ejemplo, solo hay un int value que se comparte entre todas las instancias de Foo porque se declara static . (Consulte el tutorial “Comprensión de los miembros de la clase” ).

Si agrega varios objetos Foo a una lista usando el siguiente código, cada instancia devolverá 3 de una llamada a getValue() :

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

La solución es simple: no use las palabras clave static para los campos de su clase a menos que realmente desee que los valores se compartan entre cada instancia de esa clase.

Agregar el mismo objeto

Si agrega una variable temporal a una lista, debe crear una nueva instancia cada vez que realice un ciclo. Considere el siguiente fragmento de código erróneo:

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

Aquí, el objeto tmp se construyó fuera del bucle. Como resultado, la misma instancia de objeto se agrega a la lista tres veces. La instancia tendrá el valor 2 , porque ese fue el valor pasado durante la última llamada a setValue() .

Para arreglar esto, solo mueve la construcción del objeto dentro del ciclo:

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

Su problema es con el tipo static que requiere una nueva inicialización cada vez que se repite un ciclo. Si está en un bucle, es mejor mantener la inicialización concreta dentro del bucle.

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

En lugar 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 } 

Aquí la tag es una variable en SomeStaticClass para verificar la validez del fragmento de arriba; puede tener alguna otra implementación basada en su caso de uso.

Cada vez que agrega un objeto a una ArrayList, asegúrese de agregar un objeto nuevo y un objeto que ya no esté usado. Lo que está sucediendo es que cuando agrega la misma 1 copia de objeto, ese mismo objeto se agrega a diferentes posiciones en una ArrayList. Y cuando realiza el cambio a uno, porque la misma copia se agrega una y otra vez, todas las copias se ven afectadas. Por ejemplo, supongamos que tienes una ArrayList como esta:

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

Ahora si agrega esta Tarjeta c a la lista, se agregará sin problema. Se guardará en la ubicación 0. Pero, cuando guarde la misma Tarjeta c en la lista, se guardará en la ubicación 1. Recuerde que agregó el mismo objeto a dos ubicaciones diferentes en una lista. Ahora bien, si realiza un cambio en el objeto Card c, los objetos en una lista en la ubicación 0 y 1 también reflejarán ese cambio, porque son el mismo objeto.

Una solución sería crear un constructor en la clase de la Tarjeta, que acepte otro objeto de la Tarjeta. Luego, en ese constructor, puede establecer las propiedades de esta manera:

 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 } 

Y digamos que tiene la misma 1 copia de la Tarjeta, por lo que al momento de agregar un nuevo objeto, puede hacer esto:

 list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf)); 

Tuve el mismo problema con la instancia del calendario.

Codigo erroneo:

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

Tienes que crear un objeto NUEVO del calendario, que se puede hacer con 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); }