Java: ajouter des éléments à une collection pendant l'itération

est-il possible d'ajouter des éléments à une collection tout en itérant dessus?

Plus précisément, je voudrais effectuer une itération sur une collection, et si un élément satisfait une certaine condition, je veux ajouter quelques autres éléments de la collection, et assurez-vous que ces éléments sont itéré. (Je me rends compte que ce pourrait conduire à une boucle interminable, mais je suis presque sûr que ce ne sera pas dans mon cas.)

Le" tutoriel Java 151980920 de Sun suggère que ce n'est pas possible: "notez que Iterator.remove est la seulement façon sûre de modifier une collection pendant l'itération; le comportement est non spécifié si la collection sous-jacente est modifiée de toute autre manière pendant que l'itération est en cours."

donc si Je ne peux pas faire ce que je veux en utilisant des itérateurs, que suggérez-vous que je fasse?

69
demandé sur grifaton 2009-06-14 19:32:02

17 réponses

Que Diriez-vous de construire une file d'attente avec les éléments que vous voulez itérer; quand vous voulez ajouter des éléments, interrogez-les à la fin de la file d'attente, et continuez à supprimer des éléments jusqu'à ce que la file d'attente soit vide. C'est ainsi que fonctionne habituellement une recherche étendue.

56
répondu Avi 2009-06-14 15:35:28

il y a deux questions ici:

le premier numéro est, en ajoutant à un Collection après un Iterator est retourné. Comme mentionné, il n'y a pas de comportement défini lorsque le Collection sous-jacent est modifié, comme indiqué dans la documentation pour Iterator.remove :

... Le comportement d'un itérateur est non spécifié si le sous-jacent la collecte est modifiée pendant que le l'itération en cours, en quelque sorte d'autres que par l'appel de cette méthode.

la deuxième question est, même si un Iterator pouvait être obtenu, puis revenir au même élément que le Iterator était at, il n'y a aucune garantie quant à l'ordre de l'itération, comme indiqué dans le Collection.iterator documentation de la méthode:

... Il n'existe aucune garantie concernant l' l'ordre dans lequel les éléments sont retournés (à moins que cette collection est une instance d'une classe qui fournit un garantir.)

Par exemple, disons que nous avons la liste [1, 2, 3, 4] .

disons 5 a été ajouté lorsque le Iterator était à 3 , et d'une certaine façon, nous obtenons un Iterator qui peut reprendre l'itération de 4 . Cependant, il n'y a pas de garantie que 5 viendra après 4 . L'ordre d'itération peut être [5, 1, 2, 3, 4] -- puis l'itérateur manquera toujours l'élément 5 .

comme il n'y a aucune garantie au comportement, on ne peut pas supposer que les choses se produiront d'une certaine manière.

une alternative pourrait être d'avoir un Collection séparé auquel les éléments nouvellement créés peuvent être ajoutés, puis itérer sur ces éléments:

Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"});
Collection<String> additionalList = new ArrayList<String>();

for (String s : list) {
    // Found a need to add a new element to iterate over,
    // so add it to another list that will be iterated later:
    additionalList.add(s);
}

for (String s : additionalList) {
    // Iterate over the elements that needs to be iterated over:
    System.out.println(s);
}

Modifier

Elaboration sur Avi's réponse , il est possible de mettre en file d'attente les éléments que nous voulons itérer dans une file d'attente, et de supprimer les éléments alors que la file d'attente a des éléments. Cela permettra à l' "itération" sur les nouveaux éléments en plus des éléments d'origine.

regardons comment cela fonctionnerait.

conceptuellement, si nous avons les éléments suivants dans la file d'attente:

[1, 2, 3, 4]

et, lorsque nous enlevons 1 , nous décidons d'ajouter 42 , la file d'attente sera la suivante:

[2, 3, 4, 42]

comme la file d'attente est une structure de données FIFO (premier entré, premier sorti), cet ordre est typique. (Comme indiqué dans la documentation pour l'interface Queue , ce n'est pas une nécessité d'un Queue . Prenons le cas de PriorityQueue qui commande les éléments par leur ordre naturel, donc ce N'est pas FIFO.)

le texte suivant est un exemple d'utilisation d'un LinkedList (qui est un Queue ) afin de passer en revue tous les éléments avec des éléments supplémentaires ajoutés pendant le dequeing. Similaire à l'exemple ci-dessus, l'élément 42 est ajouté lorsque l'élément 2 est supprimé:

Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);

while (!queue.isEmpty()) {
    Integer i = queue.remove();
    if (i == 2)
        queue.add(42);

    System.out.println(i);
}

Le résultat est le suivant:

1
2
3
4
42

comme espéré, l'élément 42 qui a été ajouté lorsque nous avons appuyé sur 2 est apparu.

44
répondu coobird 2017-05-23 12:34:35

vous pouvez également regarder certains des types plus spécialisés, comme ListIterator , NavigableSet et (si vous êtes intéressé par les cartes) NavigableMap .

8
répondu McDowell 2009-06-14 16:20:17

en fait, il est assez facile. Pensez juste à la manière optimale. Je crois que la voie optimale est:

for (int i=0; i<list.size(); i++) {
   Level obj = list.get(i);

   //Here execute yr code that may add / or may not add new element(s)
   //...

   i=list.indexOf(obj);
}

l'exemple suivant fonctionne parfaitement dans le cas le plus logique - lorsque vous n'avez pas besoin d'itérer les nouveaux éléments ajoutés avant l'élément d'itération. Sur les éléments ajoutés après l'itération élément - là, vous pourriez ne pas les réitérer. Dans ce cas, vous devriez simplement ajouter / ou étendre l'objet yr avec un drapeau qui les marquera les réitérer.

3
répondu PatlaDJ 2010-01-16 22:15:46

utilisant des itérateurs ... Non, Je ne pense pas. Vous devrez Hacker ensemble quelque chose comme ceci:

    Collection< String > collection = new ArrayList< String >( Arrays.asList( "foo", "bar", "baz" ) );
    int i = 0;
    while ( i < collection.size() ) {

        String curItem = collection.toArray( new String[ collection.size() ] )[ i ];
        if ( curItem.equals( "foo" ) ) {
            collection.add( "added-item-1" );
        }
        if ( curItem.equals( "added-item-1" ) ) {
            collection.add( "added-item-2" );
        }

        i++;
    }

    System.out.println( collection );

que yeilds:

[foo, bar, baz, ajoute-item-1, ajoutée à l'élément 2]

1
répondu javamonkey79 2009-06-14 16:40:10
public static void main(String[] args)
{
    // This array list simulates source of your candidates for processing
    ArrayList<String> source = new ArrayList<String>();
    // This is the list where you actually keep all unprocessed candidates
    LinkedList<String> list = new LinkedList<String>();

    // Here we add few elements into our simulated source of candidates
    // just to have something to work with
    source.add("first element");
    source.add("second element");
    source.add("third element");
    source.add("fourth element");
    source.add("The Fifth Element"); // aka Milla Jovovich

    // Add first candidate for processing into our main list
    list.addLast(source.get(0));

    // This is just here so we don't have to have helper index variable
    // to go through source elements
    source.remove(0);

    // We will do this until there are no more candidates for processing
    while(!list.isEmpty())
    {
        // This is how we get next element for processing from our list
        // of candidates. Here our candidate is String, in your case it
        // will be whatever you work with.
        String element = list.pollFirst();
        // This is where we process the element, just print it out in this case
        System.out.println(element);

        // This is simulation of process of adding new candidates for processing
        // into our list during this iteration.
        if(source.size() > 0) // When simulated source of candidates dries out, we stop
        {
            // Here you will somehow get your new candidate for processing
            // In this case we just get it from our simulation source of candidates.
            String newCandidate = source.get(0);
            // This is the way to add new elements to your list of candidates for processing
            list.addLast(newCandidate);
            // In this example we add one candidate per while loop iteration and 
            // zero candidates when source list dries out. In real life you may happen
            // to add more than one candidate here:
            // list.addLast(newCandidate2);
            // list.addLast(newCandidate3);
            // etc.

            // This is here so we don't have to use helper index variable for iteration
            // through source.
            source.remove(0);
        }
    }
}
1
répondu csd 2011-11-02 20:33:02

Par exemple, nous avons deux listes:

  public static void main(String[] args) {
        ArrayList a = new ArrayList(Arrays.asList(new String[]{"a1", "a2", "a3","a4", "a5"}));
        ArrayList b = new ArrayList(Arrays.asList(new String[]{"b1", "b2", "b3","b4", "b5"}));
        merge(a, b);
        a.stream().map( x -> x + " ").forEach(System.out::print);
    }
   public static void merge(List a, List b){
        for (Iterator itb = b.iterator(); itb.hasNext(); ){
            for (ListIterator it = a.listIterator() ; it.hasNext() ; ){
                it.next();
                it.add(itb.next());

            }
        }

    }

a1 b1 a2 b2 a3 b3 a4 b4 a5 B5

1
répondu Alexander Smirnov 2015-11-25 00:59:27

utiliser ListIterator comme suit:

List<String> l = new ArrayList<>();
l.add("Foo");
ListIterator<String> iter = l.listIterator(l.size());
while(iter.hasPrevious()){
    String prev=iter.previous();
    if(true /*You condition here*/){
        iter.add("Bah");
        iter.add("Etc");
    }
}

la clé est d'itérer dans l'ordre inverse - puis les éléments ajoutés apparaissent sur la prochaine itération.

1
répondu SteveR 2017-11-15 16:22:14

je préfère traiter les collections de façon fonctionnelle plutôt que de les muter en place. Cela évite ce genre de problème tout à fait, ainsi que les problèmes d'Alias et d'autres sources délicates de bogues.

donc, je l'implémenterais comme:

List<Thing> expand(List<Thing> inputs) {
    List<Thing> expanded = new ArrayList<Thing>();

    for (Thing thing : inputs) {
        expanded.add(thing);
        if (needsSomeMoreThings(thing)) {
            addMoreThingsTo(expanded);
        }
    }

    return expanded;
}
0
répondu Nat 2009-06-14 16:52:48

IMHO la manière la plus sûre serait de créer une nouvelle collection, d'itérer au-dessus de votre collection donnée, en ajoutant chaque élément dans la nouvelle collection, et en ajoutant des éléments supplémentaires si nécessaire dans la nouvelle collection ainsi, enfin retourner la nouvelle collection.

0
répondu Michael Zilbermann 2009-06-14 18:58:06

outre la solution d'utiliser une liste supplémentaire et d'appeler addAll pour insérer les nouveaux éléments après l'itération (comme par exemple la solution par L'utilisateur Nat), vous pouvez également utiliser des collections concurrentes comme le CopyOnWriteArrayList .

la méthode de l'itérateur de style "snapshot" utilise une référence à l'état du tableau au point où l'itérateur a été créé. Ce tableau ne change jamais pendant la durée de vie de l'itérateur, donc l'interférence est impossible et l'itérateur est garanti de ne pas jeter ConcurrentModificationException.

avec cette collection spéciale (habituellement utilisée pour l'accès simultané) il est possible de manipuler la liste sous-jacente tout en itérant sur elle. Toutefois, l'itérateur ne reflétera pas les changements.

Est-ce mieux que l'autre solution? Probablement pas, Je ne connais pas les frais généraux introduits par L'approche copie-sur-papier.

0
répondu dmeister 2009-06-14 21:21:52

avec une liste List<Object> que vous voulez itérer, la voie facile est:

while (!list.isEmpty()){
   Object obj = list.get(0);

   // do whatever you need to
   // possibly list.add(new Object obj1);

   list.remove(0);
}

ainsi, vous itérez à travers une liste, toujours en prenant le premier élément puis en le supprimant. De cette façon, vous pouvez ajouter de nouveaux éléments à la liste tout en itérant.

0
répondu Alina 2013-02-08 07:45:43

Oubliez les itérateurs, ils ne fonctionnent pas pour ajouter, seulement pour supprimer. Ma réponse ne s'applique qu'aux listes, alors ne me punissez pas pour ne pas avoir résolu le problème des collections. Tenir à l'essentiel:

    List<ZeObj> myList = new ArrayList<ZeObj>();
    // populate the list with whatever
            ........
    int noItems = myList.size();
    for (int i = 0; i < noItems; i++) {
        ZeObj currItem = myList.get(i);
        // when you want to add, simply add the new item at last and
        // increment the stop condition
        if (currItem.asksForMore()) {
            myList.add(new ZeObj());
            noItems++;
        }
    }
0
répondu Victor Ionescu 2013-10-31 07:45:36

J'ai fatigué ListIterator mais cela n'a pas aidé mon cas, où vous devez utiliser la liste tout en y ajoutant. Voici ce qui fonctionne pour moi:

Utiliser LinkedList .

LinkedList<String> l = new LinkedList<String>();
l.addLast("A");

while(!l.isEmpty()){
    String str = l.removeFirst();
    if(/* Condition for adding new element*/)
        l.addLast("<New Element>");
    else
        System.out.println(str);
}

cela pourrait donner une exception ou courir dans des boucles infinies. Cependant, comme vous l'avez mentionné

je suis assez sûr qu'il ne sera pas dans mon cas

les cases à cocher dans un tel code sont votre responsabilité.

0
répondu Vigya Sharma 2014-06-21 18:47:52

C'est ce que je fais habituellement, avec des collections comme des ensembles:

Set<T> adds = new HashSet<T>, dels = new HashSet<T>;
for ( T e: target )
  if ( <has to be removed> ) dels.add ( e );
  else if ( <has to be added> ) adds.add ( <new element> )

target.removeAll ( dels );
target.addAll ( adds );

cela crée de la mémoire supplémentaire (les pointeurs pour les ensembles intermédiaires, mais aucun élément dupliqué ne se produit) et des étapes supplémentaires (itération à nouveau au cours des changements), cependant habituellement ce n'est pas une grosse affaire et il pourrait être préférable de travailler avec une copie de collection initiale.

0
répondu zakmck 2015-01-14 11:35:03

même si nous ne pouvons pas ajouter d'éléments à la même Liste pendant l'itération, nous pouvons utiliser le flatMap de Java 8, pour ajouter de nouveaux éléments à un flux. Cela peut être fait à une condition. Après cela, l'article Ajouté peut être traité.

voici un exemple Java qui montre comment ajouter au flux en cours un objet dépendant d'une condition qui est alors traitée avec une condition:

List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);

intList = intList.stream().flatMap(i -> {
    if (i == 2) return Stream.of(i, i * 10); // condition for adding the extra items
    return Stream.of(i);
}).map(i -> i + 1)
        .collect(Collectors.toList());

System.out.println(intList);

la sortie de l'exemple de jouet est:

[2, 3, 21, 4]

0
répondu gil.fernandes 2017-08-21 16:24:43

Dans général , il n'est pas sûr, même si, pour certaines collections, il peut être. La solution évidente est d'utiliser une sorte de boucle. Mais vous n'avez pas dit quelle collection vous utilisez, donc cela peut être ou peut ne pas être possible.

-1
répondu Matthew Flaschen 2009-06-14 15:36:26