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

83

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ónica para los numerosos problemas similares que surgen en este sitio.

Duncan Jones
fuente

Respuestas:

147

Este problema tiene dos causas típicas:

  • Campos estáticos utilizados por los objetos que almacenó 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. Considere 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 uno int valueque se comparte entre todas las instancias de Fooporque está declarado static. (Consulte el tutorial "Comprensión de los miembros de la clase" ).

Si agrega varios Fooobjetos a una lista con el código siguiente, cada instancia volverá 3de una llamada a getValue():

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

La solución es simple: no use las staticpalabras clave 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 del objeto que está agregando, cada vez que realice un bucle. Considere el siguiente fragmento de código erróneo:

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

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

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

Para solucionar esto, simplemente mueva la construcción del objeto dentro del ciclo:

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);
}
Duncan Jones
fuente
1
hola @ Duncan buena solución, quiero preguntarle que en "Agregar el mismo objeto", ¿por qué tres instancias diferentes tendrán el valor 2, no las tres instancias deberían contener 3 valores diferentes? Espero que responda pronto, gracias
Dev
3
@Dev Porque el mismo objeto ( tmp) se agrega a la lista tres veces. Y ese objeto tiene un valor de dos, debido a la llamada a tmp.setValue(2)en la iteración final del ciclo.
Duncan Jones
1
ok, entonces el problema se debe al mismo objeto, ¿es así que si agrego el mismo objeto tres veces en una lista de matrices, las tres ubicaciones de arraylist obj se referirán al mismo objeto?
Desarrollo
1
@Dev Sí, eso es exactamente.
Duncan Jones
1
Un hecho obvio que inicialmente pasé por alto: con respecto a la parte you must create a new instance each time you loopde la sección Adding the same object: tenga en cuenta que la instancia a la que se hace referencia se refiere al objeto que está agregando, NO al objeto al que lo está agregando.
al
8

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

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

En vez de:

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
}

Aquí taghay una variable SomeStaticClasspara verificar la validez del fragmento anterior; puede tener alguna otra implementación basada en su caso de uso.

Shashank
fuente
¿Qué quieres decir con "tipo static"? ¿Qué sería una clase no estática para ti?
4castle
por ejemplo, no estático: public class SomeClass{/*some code*/} y estática: public static class SomeStaticClass{/*some code*/}. Espero que ahora esté más claro.
Shashank
1
Dado que, todos los objetos de una clase estática comparten la misma dirección, si se inicializan en un bucle y se establecen con valores diferentes en cada iteración. Todos ellos terminarán teniendo un valor idéntico que será igual al valor de la última iteración o cuando se modificó el último objeto. Espero que ahora esté más claro.
Shashank
6

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

}
basti12354
fuente
4

Cada vez que agregue un objeto a una ArrayList, asegúrese de agregar un nuevo objeto y no un objeto ya utilizado. 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 cambia uno, porque se agrega la misma copia una y otra vez, todas las copias se ven afectadas. Por ejemplo, digamos que tiene una ArrayList como esta:

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

Ahora, si agrega esta Tarjeta c a la lista, se agregará sin problemas. Se guardará en la ubicación 0. Pero, cuando guarde la misma Tarjeta c en la lista, se guardará en la ubicación 1. Así que recuerde que agregó el mismo 1 objeto a dos ubicaciones diferentes en una lista. Ahora bien, si realiza un cambio en el objeto Tarjeta c, los objetos de 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 hacer un constructor en la clase Card, que acepte otro objeto Card. 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 copia 1 de Card, por lo que al momento de agregar un nuevo objeto, puede hacer esto:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));
Faraz
fuente
2

También puede ser consecuencia de utilizar la misma referencia en lugar de utilizar una nueva.

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

 setdata();
......

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

En vez de:

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

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
Shaikh Mohib
fuente