web-dev-qa-db-fra.com

Pourquoi ma liste de tableaux contient-elle N copies du dernier élément ajouté à la liste?

J'ajoute trois objets différents à un ArrayList, mais la liste contient trois copies du dernier objet que j'ai ajouté.

Par exemple:

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

Attendu:

0
1
2

Réel:

2
2
2

Quelle erreur ai-je faite?

Remarque: ceci est conçu pour être une question canonique pour les nombreux problèmes similaires rencontrés sur ce site.

71
Duncan Jones

Ce problème a deux causes typiques:

  • Champs statiques utilisés par les objets que vous avez stockés dans la liste

  • Ajout accidentel du même objet à la liste

Champs statiques

Si les objets de votre liste stockent des données dans des champs statiques, chaque objet de votre liste semblera identique, car ils contiennent les mêmes valeurs. Considérez la classe ci-dessous:

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!

  public Foo(int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }
}

Dans cet exemple, il n'y a qu'un seul int value Partagé entre toutes les instances de Foo car il est déclaré static. (Voir "Comprendre les membres de la classe" tutoriel.)

Si vous ajoutez plusieurs objets Foo à une liste à l'aide du code ci-dessous, chaque instance renverra 3 À partir d'un appel à getValue():

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

La solution est simple: n'utilisez pas les mots-clés static pour les champs de votre classe, sauf si vous souhaitez réellement que les valeurs soient partagées entre toutes les instances de cette classe.

Ajout du même objet

Si vous ajoutez une variable temporaire à une liste, vous devez créer une nouvelle instance de l'objet que vous ajoutez, à chaque boucle. Considérez l'extrait de code erroné suivant:

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

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

Ici, l'objet tmp a été construit en dehors de la boucle. En conséquence, la même instance d'objet est ajoutée à la liste à trois reprises. L'instance contiendra la valeur 2, Car il s'agissait de la valeur transmise lors du dernier appel à setValue().

Pour résoudre ce problème, déplacez simplement la construction de l'objet à l'intérieur de la boucle:

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);
}
125
Duncan Jones

Votre problème concerne le type static qui nécessite une nouvelle initialisation chaque fois qu'une boucle est itérée. Si vous êtes dans une boucle, il est préférable de conserver l'initialisation concrète à l'intérieur de la boucle.

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

Au lieu 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
}

Ici tag est une variable dans SomeStaticClass pour vérifier la validité de l'extrait de code ci-dessus; vous pouvez avoir une autre implémentation basée sur votre cas d'utilisation.

6
Shashank

Avait le même problème avec l'instance de calendrier.

Mauvais code:

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

Vous devez créer un nouvel objet du calendrier, ce qui peut être fait avec 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
basti12354

Chaque fois que vous ajoutez un objet à un tableau de tableaux, assurez-vous d'ajouter un nouvel objet et un objet non déjà utilisé. En réalité, lorsque vous ajoutez la même copie d'un objet, ce même objet est ajouté à différentes positions dans un ArrayList. Et lorsque vous apportez une modification à un, parce que la même copie est ajoutée à plusieurs reprises, toutes les copies sont affectées. Par exemple, supposons que vous ayez une liste de tableaux comme celle-ci:

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

Maintenant, si vous ajoutez cette carte à la liste, ce ne sera ajouté aucun problème. Il sera enregistré à l'emplacement 0. Toutefois, lorsque vous enregistrez la même carte c dans la liste, il sera enregistré à l'emplacement 1. N'oubliez donc pas que vous avez ajouté le même objet à deux emplacements différents de la liste. Maintenant, si vous apportez une modification à l'objet Card c, les objets d'une liste des emplacements 0 et 1 refléteront également cette modification, car il s'agit du même objet.

Une solution serait de créer un constructeur dans la classe Card, qui accepte un autre objet Card. Ensuite, dans ce constructeur, vous pouvez définir les propriétés comme suit:

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
}

Et disons que vous avez la même copie de Card, donc au moment d'ajouter un nouvel objet, vous pouvez faire ceci:

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));
3
Faraz Durrani