Al ser algo nuevo en el lenguaje Java, estoy tratando de familiarizarme con todas las formas (o al menos las no patológicas) que uno podría recorrer en iteración a través de una lista (o quizás otras colecciones) y las ventajas o desventajas de cada una.
Dado un List<E> list
objeto, conozco las siguientes formas de recorrer todos los elementos:
Básico para bucle (por supuesto, también hay bucles while
/ equivalentes do while
)
// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
Nota: Como señaló @amarseillan, este formulario es una mala elección para iterar sobre List
s, porque la implementación real del get
método puede no ser tan eficiente como cuando se usa un Iterator
. Por ejemplo,LinkedList
implementaciones deben atravesar todos los elementos que preceden a i para obtener el elemento i-ésimo.
En el ejemplo anterior, no hay forma de que la List
implementación "guarde su lugar" para que las iteraciones futuras sean más eficientes. Para un ArrayList
no importa realmente, porque la complejidad / costo de get
es tiempo constante (O (1)) mientras que para a LinkedList
es proporcional al tamaño de la lista (O (n)).
Para obtener más información sobre la complejidad computacional de las Collections
implementaciones integradas , consulte esta pregunta .
Mejorado para bucle (bien explicado en esta pregunta )
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterador
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
ListIterator
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
Java funcional
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach , Stream.forEach , ...
(Un método de mapa de la API Stream de Java 8 (consulte la respuesta de @ i_am_zero)).
En Java 8, las clases de colección que implementan Iterable
(por ejemplo, todas las List
s) ahora tienen un forEach
método, que se puede utilizar en lugar de la instrucción for loop mostrada anteriormente. (Aquí hay otra pregunta que proporciona una buena comparación).
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
¿Qué otras formas hay, si hay alguna?
(Por cierto, mi interés no proviene en absoluto del deseo de optimizar el rendimiento ; solo quiero saber qué formularios están disponibles para mí como desarrollador).
List
interfaz específicamente?Respuestas:
Las tres formas de bucle son casi idénticas. El
for
bucle mejorado :es, de acuerdo con la Especificación del lenguaje Java , idéntico en efecto al uso explícito de un iterador con un
for
bucle tradicional . En el tercer caso, solo puede modificar el contenido de la lista eliminando el elemento actual y, luego, solo si lo hace a través deremove
método del iterador. Con la iteración basada en índices, puede modificar la lista de cualquier forma. Sin embargo, agregar o eliminar elementos que vienen antes del índice actual corre el riesgo de que su ciclo omita elementos o procese el mismo elemento varias veces; debe ajustar el índice de bucle correctamente cuando realice dichos cambios.En todos los casos,
element
es una referencia al elemento de lista real. Ninguno de los métodos de iteración hace una copia de nada en la lista. Los cambios en el estado interno deelement
siempre se verán en el estado interno del elemento correspondiente en la lista.Esencialmente, solo hay dos formas de iterar sobre una lista: usando un índice o usando un iterador. El bucle for mejorado es solo un atajo sintáctico introducido en Java 5 para evitar el tedio de definir explícitamente un iterador. Para ambos estilos, se puede llegar a las variaciones esencialmente triviales utilizando
for
,while
odo while
bloques, pero todas se reducen a lo mismo (o, más bien, dos cosas).EDITAR: Como @ iX3 señala en un comentario, puede usar a
ListIterator
para establecer el elemento actual de una lista a medida que itera. Debería usar enList#listIterator()
lugar deList#iterator()
inicializar la variable de bucle (que, obviamente, debería declararse como un enListIterator
lugar de unIterator
).fuente
e = iterator.next()
entonces,e = somethingElse
simplemente cambia a qué objetoe
se refiere en lugar de cambiar la tienda real de la queiterator.next()
recuperó el valor.e
no cambiará lo que está en la lista; usted tiene que llamarlist.set(index, thing)
. Puede cambiar el contenido dee
(por ejemplo,e.setSomething(newValue)
), pero para cambiar qué elemento está almacenado en la lista a medida que lo itera, debe seguir con una iteración basada en índices.e
, tendría que llamar a uno dee
los métodos porque la asignación solo cambia el puntero (perdón por mi C).Integer
yString
no sería posible cambiar el contenido usandofor-each
oIterator
métodos, tendría que manipular el objeto de la lista para reemplazar a los elementos. ¿Está bien?Ejemplo de cada tipo enumerado en la pregunta:
ListIterationExample.java
fuente
No se recomienda el bucle básico, ya que no conoce la implementación de la lista.
Si esa era una LinkedList, cada llamada a
estaría iterando sobre la lista, resultando en una complejidad de tiempo N ^ 2.
fuente
Una iteración de estilo JDK8:
fuente
En Java 8 tenemos múltiples formas de iterar sobre las clases de colección.
Usando Iterable para cada uno
Las colecciones que implementan
Iterable
(por ejemplo, todas las listas) ahora tienenforEach
método. Podemos usar el método de referencia introducido en Java 8.Uso de secuencias para cada uno y para cada pedido
También podemos iterar sobre una lista usando Stream como:
Deberíamos preferir
forEachOrdered
más queforEach
porque el comportamiento deforEach
es explícitamente no determinista, ya que a medida queforEachOrdered
realiza una acción para cada elemento de esta secuencia, en el orden de encuentro de la secuencia si la secuencia tiene un orden de encuentro definido. Por lo tanto, forEach no garantiza que se mantenga el pedido.La ventaja de las transmisiones es que también podemos hacer uso de transmisiones paralelas siempre que sea apropiado. Si el objetivo es solo imprimir los artículos independientemente del pedido, entonces podemos usar la transmisión paralela como:
fuente
No sé qué considera patológico, pero permítame brindarle algunas alternativas que no podría haber visto antes:
O su versión recursiva:
Además, una versión recursiva de lo clásico
for(int i=0...
:Los menciono porque eres "algo nuevo en Java" y esto podría ser interesante.
fuente
while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }
. Eso es más efectivo que la primera versión;).Puede usar forEach a partir de Java 8:
fuente
Para una búsqueda hacia atrás debe usar lo siguiente:
Si desea conocer una posición, use iterator.previousIndex (). También ayuda a escribir un bucle interno que compara dos posiciones en la lista (los iteradores no son iguales).
fuente
Correcto, se enumeran muchas alternativas. Lo más fácil y más limpio sería simplemente usar la
for
declaración mejorada como se muestra a continuación. ElExpression
es de algún tipo que es iterable.Por ejemplo, para recorrer en iteración los ID de lista <String>, podemos simplemente así,
fuente
En
java 8
puede usar elList.forEach()
método conlambda expression
para iterar sobre una lista.fuente
eugene82
la respuesta yi_am_zero
la respuesta de ?Siempre puede cambiar el primer y el tercer ejemplo con un ciclo while y un poco más de código. Esto le brinda la ventaja de poder usar do-while:
Por supuesto, este tipo de cosas podría causar una NullPointerException si list.size () devuelve 0, porque siempre se ejecuta al menos una vez. Esto se puede solucionar probando si el elemento es nulo antes de usar sus atributos / métodos. Aún así, es mucho más simple y fácil usar el bucle for
fuente