¿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 asegurarme de que estos elementos agregados también se repitan. (Me doy cuenta de que esto podría llevar a un bucle indeterminado, 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 cualquier otra manera mientras la iteración está en curso".
Entonces, si no puedo hacer lo que quiero hacer usando iteradores, ¿qué sugieres que haga?
ListIterator iter = list.listIterator()
tiene ambos métodosadd()
yremove()
, por lo que puede agregar y eliminar elementos durante la iteraciónLinkedBlockingQueue
Hay dos problemas aquí:
El primer problema es agregar a un
Collection
después de queIterator
se devuelve un. Como se mencionó, no hay un comportamiento definido cuandoCollection
se modifica el subyacente , como se indica en la documentación paraIterator.remove
:El segundo problema es que, incluso si se
Iterator
pudiera obtener un, y luego regresar al mismo elemento en el queIterator
estaba, no hay garantía sobre el orden de la iteración, como se indica en laCollection.iterator
documentación del método:Por ejemplo, digamos que tenemos la lista
[1, 2, 3, 4]
.Digamos que
5
se agregó cuandoIterator
estaba en3
, y de alguna manera, obtenemos unIterator
que puede reanudar la iteración4
. Sin embargo, no hay garantía de que5
vendrá después4
. El orden de iteración puede ser[5, 1, 2, 3, 4]
, entonces el iterador aún perderá el elemento5
.Como no hay garantía para el comportamiento, no se puede asumir que las cosas sucederán de cierta manera.
Una alternativa podría ser tener un elemento separado
Collection
al que se puedan agregar los elementos recién creados y luego iterar sobre esos elementos: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); }
Editar
Abundando en la respuesta de Avi , es posible hacer cola los elementos que queremos para repetir en una cola, y retirar los elementos, mientras que 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 eliminemos
1
, decidamos agregar42
, la cola quedará como 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
Queue
interfaz, esto no es una necesidad de aQueue
. Tomemos el caso dePriorityQueue
que ordena los elementos por su orden natural, por lo que no es FIFO).El siguiente es un ejemplo que usa a
LinkedList
(que es aQueue
) para pasar por todos los elementos junto con los elementos adicionales agregados durante la eliminación de la cola. Al igual que en el ejemplo anterior, el elemento42
se agrega cuando2
se elimina el elemento :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); }
El resultado es el siguiente:
1 2 3 4 42
Como era de esperar, apareció el elemento
42
que se agregó cuando golpeamos2
.fuente
También es posible que desee ver en algunos de los tipos más especializados, como ListIterator , NavigableSet y (si usted está interesado en los mapas) NavigableMap .
fuente
De hecho, es bastante fácil. Piense en la forma óptima. Creo que la forma óptima es:
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); }
El siguiente ejemplo funciona perfectamente en el caso más lógico, cuando no necesita iterar los elementos nuevos agregados antes del elemento de iteración. Acerca de los elementos agregados después del elemento de iteración, es posible que tampoco desee iterarlos. En este caso, simplemente debe agregar / o extender el objeto yr con una bandera que los marcará para no iterarlos.
fuente
Úselo de la
ListIterator
siguiente manera: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 clave es iterar en orden inverso ; luego, los elementos agregados aparecen en la siguiente iteración.
fuente
Sé que ha sido bastante antiguo. Pero pensé que sería útil para cualquier otra persona. Recientemente me encontré con este problema similar en el que necesito una cola que se pueda modificar durante la iteración. Usé listIterator para implementar lo mismo en las mismas líneas que lo que sugirió Avi -> Respuesta de Avi . Vea si esto se adapta a sus necesidades.
ModifyWhileIterateQueue.java
import java.util.ArrayList; import java.util.List; import java.util.ListIterator; public class ModifyWhileIterateQueue<T> { ListIterator<T> listIterator; int frontIndex; List<T> list; public ModifyWhileIterateQueue() { frontIndex = 0; list = new ArrayList<T>(); listIterator = list.listIterator(); } public boolean hasUnservicedItems () { return frontIndex < list.size(); } public T deQueue() { if (frontIndex >= list.size()) { return null; } return list.get(frontIndex++); } public void enQueue(T t) { listIterator.add(t); } public List<T> getUnservicedItems() { return list.subList(frontIndex, list.size()); } public List<T> getAllItems() { return list; } }
ModifyWhileIterateQueueTest.java
@Test public final void testModifyWhileIterate() { ModifyWhileIterateQueue<String> queue = new ModifyWhileIterateQueue<String>(); queue.enQueue("one"); queue.enQueue("two"); queue.enQueue("three"); for (int i=0; i< queue.getAllItems().size(); i++) { if (i==1) { queue.enQueue("four"); } } assertEquals(true, queue.hasUnservicedItems()); assertEquals ("[one, two, three, four]", ""+ queue.getUnservicedItems()); assertEquals ("[one, two, three, four]", ""+queue.getAllItems()); assertEquals("one", queue.deQueue()); }
fuente
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é resultados:
[foo, bar, baz, added-item-1, added-item-2]
fuente
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 .
Con esta colección especial (generalmente utilizada para acceso concurrente) 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 conozco la sobrecarga introducida por el enfoque Copy-On-Write.
fuente
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); } } }
fuente
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
fuente
Prefiero procesar colecciones funcionalmente en lugar de mutarlas en su lugar. Eso evita este tipo de problema por completo, así como los problemas de aliasing y otras fuentes complicadas de errores.
Entonces, lo implementaría como:
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; }
fuente
En mi humilde opinión, la forma más segura sería crear una nueva colección, iterar sobre su colección dada, agregar cada elemento en la nueva colección y agregar elementos adicionales según sea necesario en la nueva colección también, finalmente devolviendo la nueva colección.
fuente
Dada una lista sobre la
List<Object>
que desea iterar, la forma más sencilla es:while (!list.isEmpty()){ Object obj = list.get(0); // do whatever you need to // possibly list.add(new Object obj1); list.remove(0); }
Entonces, iteras a través de una lista, siempre tomando el primer elemento y luego eliminándolo. De esta manera, puede agregar nuevos elementos a la lista mientras itera.
fuente
Olvídese de los iteradores, no sirven para agregar, solo para eliminar. Mi respuesta se aplica solo a las listas, así que no me castigue por no resolver el problema de las colecciones. Cíñete a lo básico:
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++; } }
fuente
Cansé ListIterator pero no ayudó en mi caso, donde tienes que usar la lista mientras agregas. Esto es lo que funciona para mí:
Utilice 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); }
Esto podría dar una excepción o encontrarse con bucles infinitos. Sin embargo, como has mencionado
comprobar los casos de las esquinas en dicho código es su responsabilidad.
fuente
Esto es lo que suelo hacer, con colecciones como conjuntos:
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 );
Esto crea algo de memoria adicional (los punteros para conjuntos intermedios, pero no ocurren elementos duplicados) y pasos adicionales (iterando nuevamente sobre los cambios), sin embargo, generalmente eso no es un gran problema y podría ser mejor que trabajar con una copia de colección inicial.
fuente
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 con una condición. Después de esto, se puede procesar el artículo agregado.
Aquí hay un ejemplo de Java que muestra cómo agregar un objeto a la transmisión en curso dependiendo de una condición que luego se procesa con una condición:
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);
El resultado del ejemplo del juguete es:
fuente
En general , no es seguro, aunque para algunas colecciones puede serlo. La alternativa obvia es utilizar algún tipo de bucle for. Pero no dijiste qué colección estás usando, por lo que puede que sea posible o no.
fuente