español - Formas de iterar sobre una lista en Java
recorrer arraylist java foreach (11)
Correcto, se enumeran muchas alternativas. Lo más fácil y limpio sería usar la declaración mejorada como se muestra a continuación. La Expression
es de algún tipo que es iterable.
for ( FormalParameter : Expression ) Statement
Por ejemplo, para iterar a través de los ID de <String> de la lista, podemos simplemente así,
for (String str : ids) {
// Do something
}
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 a través de una lista (o quizás otras colecciones) y las ventajas o desventajas de cada una.
Dado un objeto de List<E> list
, conozco las siguientes formas de recorrer todos los elementos:
Básico for loop (por supuesto, también hay bucles while
/ do while
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 @amarseillan señaló, esta forma es una mala elección para iterar sobre listas s, ya que la implementación real del método get
puede no ser tan eficiente como cuando se usa un Iterator
. Por ejemplo, las implementaciones de LinkedList
deben atravesar todos los elementos que preceden a i para obtener el elemento i-th.
En el ejemplo anterior, no hay forma de que la implementación de la List
"guarde su lugar" para hacer que las futuras iteraciones sean más eficientes. Para un ArrayList
no importa realmente, porque la complejidad / costo de get
es tiempo constante (O (1)), mientras que para un LinkedList
es proporcional al tamaño de la lista (O (n)).
Para obtener más información sobre la complejidad computacional de las implementaciones de Collections
integradas, consulte esta pregunta .
Mejorado para bucle (bien explicado en esta pregunta )
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterator
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 (ver la respuesta de @ i_am_zero).)
En Java 8, las clases de colección que implementan Iterable
(por ejemplo, todas las List
) ahora tienen un método forEach
, que se puede usar en lugar de la for demostrada 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 las hay?
(Por cierto, mi interés no se deriva en absoluto del deseo de optimizar el rendimiento ; solo quiero saber qué formularios están disponibles para mí como desarrollador).
Ejemplo de cada tipo enumerado en la pregunta:
ListIterationExample.java
import java.util.*;
public class ListIterationExample {
public static void main(String []args){
List<Integer> numbers = new ArrayList<Integer>();
// populates list with initial values
for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
numbers.add(i);
printList(numbers); // 0,1,2,3,4,5,6,7
// replaces each element with twice its value
for (int index=0; index < numbers.size(); index++) {
numbers.set(index, numbers.get(index)*2);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// does nothing because list is not being changed
for (Integer number : numbers) {
number++; // number = new Integer(number+1);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// same as above -- just different syntax
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
number++;
}
printList(numbers); // 0,2,4,6,8,10,12,14
// ListIterator<?> provides an "add" method to insert elements
// between the current element and the cursor
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.add(number+1); // insert a number right before this
}
printList(numbers); // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
// Iterator<?> provides a "remove" method to delete elements
// between the current element and the cursor
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
if (number % 2 == 0) // if number is even
iter.remove(); // remove it from the collection
}
printList(numbers); // 1,3,5,7,9,11,13,15
// ListIterator<?> provides a "set" method to replace elements
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.set(number/2); // divide each element by 2
}
printList(numbers); // 0,1,2,3,4,5,6,7
}
public static void printList(List<Integer> numbers) {
StringBuilder sb = new StringBuilder();
for (Integer number : numbers) {
sb.append(number);
sb.append(",");
}
sb.deleteCharAt(sb.length()-1); // remove trailing comma
System.out.println(sb.toString());
}
}
El bucle básico no se recomienda ya que no conoce la implementación de la lista.
Si eso era un LinkedList, cada llamada a
list.get(i)
estaría iterando sobre la lista, resultando en una complejidad de tiempo N ^ 2.
En java 8
puede usar el método List.forEach()
con la lambda expression
para iterar sobre una lista.
import java.util.ArrayList;
import java.util.List;
public class TestA {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("Apple");
list.add("Orange");
list.add("Banana");
list.forEach(
(name) -> {
System.out.println(name);
}
);
}
}
En Java 8 tenemos múltiples formas de iterar sobre las clases de colección.
Usando Iterable para cada
Las colecciones que implementan Iterable
(por ejemplo, todas las listas) ahora tienen el método forEach
. Podemos usar el method-reference introducido en Java 8.
Arrays.asList(1,2,3,4).forEach(System.out::println);
Usando Streams para cada y para cada orden
También podemos iterar sobre una lista usando Stream como:
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);
Deberíamos preferir forEachOrdered
sobre forEach
porque el comportamiento de forEach
es explícitamente no determinista, ya que como forEachOrdered
realiza una acción para cada elemento de este flujo, en el orden de encuentro del flujo si el flujo tiene un orden de encuentro definido. Así que forEach no garantiza que el pedido se mantenga.
La ventaja de los flujos es que también podemos hacer uso de flujos paralelos siempre que sea apropiado. Si el objetivo es solo imprimir los artículos independientemente del orden, entonces podemos usar flujo paralelo como:
Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
Las tres formas de bucle son casi idénticas. El bucle mejorado for
:
for (E element : list) {
. . .
}
Es, de acuerdo con la especificación del lenguaje Java , idéntico en efecto al uso explícito de un iterador con un bucle for
tradicional. En el tercer caso, solo puede modificar el contenido de la lista eliminando el elemento actual, y solo si lo hace a través del método de remove
del propio iterador. Con la iteración basada en índices, puede modificar la lista de cualquier manera. Sin embargo, agregar o eliminar elementos que vienen antes del índice actual corre el riesgo de que su bucle omita elementos o procese el mismo elemento varias veces; debe ajustar el índice de bucle correctamente cuando realice dichos cambios.
En todos los casos, el 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 del element
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, puedes crear variaciones esencialmente triviales usando for
, while
o do while
blocks, pero todas se reducen a la misma cosa (o, más bien, dos cosas).
EDITAR: Como @ iX3 señala en un comentario, puede usar un ListIterator
para establecer el elemento actual de una lista a medida que realiza la iteración. Necesitaría usar List#listIterator()
lugar de List#iterator()
para inicializar la variable de bucle (que, obviamente, tendría que ser declarado ListIterator
lugar de ListIterator
).
No sé qué considera patológico, pero permítame ofrecerle algunas alternativas que podría no haber visto antes:
List<E> sl= list ;
while( ! sl.empty() ) {
E element= sl.get(0) ;
.....
sl= sl.subList(1,sl.size());
}
O su versión recursiva:
void visit(List<E> list) {
if( list.isEmpty() ) return;
E element= list.get(0) ;
....
visit(list.subList(1,list.size()));
}
Además, una versión recursiva del clásico for(int i=0...
:
void visit(List<E> list,int pos) {
if( pos >= list.size() ) return;
E element= list.get(pos) ;
....
visit(list,pos+1);
}
Los menciono porque eres "algo nuevo en Java" y esto podría ser interesante.
Para una búsqueda hacia atrás debes usar lo siguiente:
for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
SomeClass item = iterator.previous();
...
item.remove(); // For instance.
}
Si desea conocer una posición, use iterator.previousIndex (). También ayuda a escribir un bucle interno que compare dos posiciones en la lista (los iteradores no son iguales).
Puedes usar forEach desde Java 8:
List<String> nameList = new ArrayList<>(
Arrays.asList("USA", "USSR", "UK"));
nameList.forEach((v) -> System.out.println(v));
Siempre se puede cambiar el primer y tercer ejemplo con un bucle while y un poco más de código. Esto le da la ventaja de poder usar do-while:
int i = 0;
do{
E element = list.get(i);
i++;
}
while (i < list.size());
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 puede solucionarse probando si el elemento es nulo antes de usar sus atributos / métodos aunque. Aún así, es mucho más simple y fácil de usar el bucle for
Una iteración al estilo JDK8:
public class IterationDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
list.stream().forEach(elem -> System.out.println("element " + elem));
}
}