Java: agregar elementos a una colección durante la iteración

¿Es posible agregar elementos a una colección mientras se itera sobre ella?

Más específicamente, me gustaría iterar sobre una colección, y si un elemento satisface una determinada condición, quiero agregar algunos otros elementos a la colección, y me aseguro de que estos elementos agregados también se repitan. (Me doy cuenta de que esto podría conducir a un ciclo de interrupción, pero estoy bastante seguro de que no lo hará en mi caso).

El Tutorial de Java de Sun sugiere que esto no es posible: “Tenga en cuenta que Iterator.remove es la única forma segura de modificar una colección durante la iteración, el comportamiento no se especifica si la colección subyacente se modifica de otra manera mientras la iteración está en progreso. ”

Entonces, si no puedo hacer lo que quiero hacer con los iteradores, ¿qué sugieres que haga?

¿Qué hay de construir una cola con los elementos que desea iterar; cuando desee agregar elementos, colóquelos en cola al final de la cola y siga eliminando elementos hasta que la cola esté vacía. Así es como generalmente funciona una búsqueda de amplitud.

Hay dos problemas aquí:

El primer problema es agregar a una Collection después de que se devuelve un Iterator . Como se mencionó, no existe un comportamiento definido cuando se modifica la Collection subyacente, como se indica en la documentación para Iterator.remove :

… El comportamiento de un iterador no se especifica si la colección subyacente se modifica mientras la iteración está en progreso de otra manera que llamando a este método.

El segundo problema es que, incluso si se pudiera obtener un Iterator y luego volver al mismo elemento en el que estaba el Iterator , no hay garantía sobre el orden de la iteración, como se indica en la documentación del método Collection.iterator :

… No hay garantías con respecto al orden en que se devuelven los elementos (a menos que esta colección sea una instancia de alguna clase que proporcione una garantía).

Por ejemplo, digamos que tenemos la lista [1, 2, 3, 4] .

Digamos que 5 se agregó cuando el Iterator estaba en 3 , y de alguna manera, obtenemos un Iterator que puede reanudar la iteración desde 4 . Sin embargo, no hay garantía de que 5 vendrá después de 4 . El orden de iteración puede ser [5, 1, 2, 3, 4] – entonces el iterador aún extrañará el elemento 5 .

Como no hay garantía para el comportamiento, no se puede suponer que las cosas sucedan de cierta manera.

Una alternativa podría ser tener una Collection separada a la que se puedan agregar los elementos recién creados y luego iterar sobre esos elementos:

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

Editar

Al elaborar la respuesta de Avi , es posible poner en cola los elementos que queremos iterar en una cola, y eliminar los elementos mientras la cola tiene elementos. Esto permitirá la “iteración” sobre los nuevos elementos además de los elementos originales.

Veamos cómo funcionaría.

Conceptualmente, si tenemos los siguientes elementos en la cola:

[1, 2, 3, 4]

Y, cuando eliminamos 1 , decidimos agregar 42 , la cola será la siguiente:

[2, 3, 4, 42]

Como la cola es una estructura de datos FIFO (primero en entrar , primero en salir), este orden es típico. (Como se indica en la documentación de la interfaz Queue , esta no es una necesidad de Queue . Tómese el caso de PriorityQueue que ordena los elementos por su orden natural, por lo que no es FIFO).

El siguiente es un ejemplo que utiliza una LinkedList (que es una Queue ) para examinar todos los elementos junto con los elementos adicionales que se agregan durante la eliminación. De forma similar al ejemplo anterior, el elemento 42 se agrega cuando se elimina el elemento 2 :

 Queue queue = new LinkedList(); 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); } 

El resultado es el siguiente:

 1 2 3 4 42 

Como se esperaba, el elemento 42 que se agregó cuando alcanzamos 2 apareció.

También es posible que desee ver algunos de los tipos más especializados, como ListIterator , NavigableSet y (si le interesan los mapas) NavigableMap .

En realidad, es bastante fácil. Solo piensa por la forma óptima. Creo que la forma óptima es:

 for (int i=0; i 

El siguiente ejemplo funciona perfectamente en el caso más lógico: cuando no necesita iterar los nuevos elementos agregados antes del elemento de iteración. Acerca de los elementos añadidos después del elemento de iteración: allí también es posible que no desee iterarlos. En este caso, simplemente debe agregar / ampliar el objeto yr con un indicador que los marcará para no iterarlos.

Usando iteradores … no, no lo creo. Tendrás que hackear juntos algo como esto:

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

Qué yeilds:
[foo, bar, baz, added-item-1, added-item-2]

 public static void main(String[] args) { // This array list simulates source of your candidates for processing ArrayList source = new ArrayList(); // This is the list where you actually keep all unprocessed candidates LinkedList list = new LinkedList(); // 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); } } } 

Por ejemplo, tenemos dos listas:

  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

Prefiero procesar colecciones de manera funcional en lugar de mutarlas en su lugar. Eso evita por completo este tipo de problema, así como problemas de aliasing y otras fonts de errores complicadas.

Entonces, lo implementaría como:

 List expand(List inputs) { List expanded = new ArrayList(); for (Thing thing : inputs) { expanded.add(thing); if (needsSomeMoreThings(thing)) { addMoreThingsTo(expanded); } } return expanded; } 

En mi humilde opinión, la manera más segura sería crear una nueva colección, iterar sobre su colección determinada, agregar cada elemento en la nueva colección y agregar elementos adicionales según sea necesario en la nueva colección, finalmente, devolver la nueva colección.

Además de la solución de usar una lista adicional y llamar a addAll para insertar los nuevos elementos después de la iteración (como, por ejemplo, la solución del usuario Nat), también puede usar colecciones concurrentes como CopyOnWriteArrayList .

El método del iterador de estilo “instantánea” utiliza una referencia al estado de la matriz en el punto en que se creó el iterador. Esta matriz nunca cambia durante la vida útil del iterador, por lo que la interferencia es imposible y se garantiza que el iterador no lanzará ConcurrentModificationException.

Con esta colección especial (generalmente utilizada para el acceso simultáneo) es posible manipular la lista subyacente mientras se itera sobre ella. Sin embargo, el iterador no reflejará los cambios.

¿Es esto mejor que la otra solución? Probablemente no, no sé los gastos generales introducidos por el enfoque de Copiar en la escritura.

Dada una lista List que desea iterar, la manera fácil de peasy es:

 while (!list.isEmpty()){ Object obj = list.get(0); // do whatever you need to // possibly list.add(new Object obj1); list.remove(0); } 

Por lo tanto, itera a través de una lista, siempre tomando el primer elemento y luego eliminándolo. De esta forma, puede agregar nuevos elementos a la lista mientras itera.

Olvídese de los iteradores, no funcionan para agregar, solo para eliminar. Mi respuesta solo se aplica a las listas, así que no me castigue por no resolver el problema de las colecciones. Aténgase a lo básico:

  List myList = new ArrayList(); // 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++; } } 

Me cansé de ListIterator, pero no ayudó en mi caso, donde tienes que usar la lista mientras la agregas. Esto es lo que funciona para mí:

Use LinkedList .

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

Esto podría dar una excepción o ejecutar en bucles infinitos. Sin embargo, como has mencionado

Estoy bastante seguro de que no será en mi caso

verificar casos de esquina en dicho código es su responsabilidad.

Esto es lo que suelo hacer, con colecciones como conjuntos:

 Set adds = new HashSet, dels = new HashSet; for ( T e: target ) if (  ) dels.add ( e ); else if (  ) adds.add (  ) target.removeAll ( dels ); target.addAll ( adds ); 

Esto crea algo de memoria extra (los punteros para conjuntos intermedios, pero no ocurren elementos duplicados) y los pasos adicionales (iterando nuevamente sobre los cambios), sin embargo, generalmente no es gran cosa y podría ser mejor que trabajar con una copia de colección inicial.

Aunque no podemos agregar elementos a la misma lista durante la iteración, podemos usar flatMap de Java 8 para agregar nuevos elementos a una secuencia. Esto se puede hacer en una condición. Después de esto, el elemento agregado se puede procesar.

Aquí hay un ejemplo de Java que muestra cómo agregar a la transmisión en curso un objeto dependiendo de una condición que luego se procesa con una condición:

 List 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 salida del ejemplo de juguete es:

[2, 3, 21, 4]

Use ListIterator siguiente manera:

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

La clave es repetir en orden inverso ; luego, los elementos agregados aparecen en la siguiente iteración.

En general , no es seguro, aunque para algunas colecciones puede ser. La alternativa obvia es usar algún tipo de bucle for. Pero no dijo qué colección está utilizando, por lo que puede o no ser posible.