Formas de iterar sobre una lista en Java


Siendo 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 iterar a través de una lista (o tal vez otras colecciones) y las ventajas o desventajas de cada una.

Dado un objeto List<E> list, conozco las siguientes formas de recorrer todos los elementos:

Básico para loop (por supuesto, hay equivalentes while / do while bucles también)

// 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 Lists, porque la implementación real de el método get puede no ser tan eficiente como cuando se utiliza un Iterator. Por ejemplo, LinkedList las implementaciones deben atravesar todos los los elementos que preceden a i para obtener el elemento i-ésimo.

En el ejemplo anterior no hay manera de que la implementación List "guarde su lugar" para que las iteraciones futuras sean más eficientes. Para un ArrayList realmente no importa, porque el la complejidad / coste 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 integradas Collections, consulte esta pregunta.

Mejorado para loop (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

    // ...
}

Funcional Java

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 de flujo de Java 8 (ver respuesta de @i_am_zero).)

En Java 8 las clases de colección que implementan Iterable (por ejemplo, todas Lists) ahora tienen un método forEach, que se puede usar en lugar de la instrucción for loop mostrada anteriormente. (Aquí está 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 maneras hay, si ¿alguna?

(Por cierto, mi interés no proviene en absoluto de un deseo de optimizar el rendimiento; solo quiero saber qué formas están disponibles para mí como desarrollador.)

Author: Peter Mortensen, 2013-08-23

11 answers

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 tradicional for. 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 remove del iterador en sí. Con la iteración basada en índices, usted es libre de modificar la lista en 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 del 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 de element siempre será visto en el estado interno del elemento correspondiente en la lista.

Esencialmente, solo hay dos formas de iterar sobre una lista: mediante el uso de un índice o mediante el uso de 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, puede llegar a variaciones esencialmente triviales utilizando for, while o do while bloques, pero todos 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 se está iterando. Usted tendría que utilizar List#listIterator() en lugar de List#iterator() para inicializar la variable de bucle (que, obviamente, tendría que ser declarada como ListIterator en lugar de como Iterator).

 215
Author: Ted Hopp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-15 21:01:52

Ejemplo de cada tipo enumerado en la pregunta:

Ejemplo de lista.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());
     }
}
 39
Author: iX3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-23 21:09:38

No se recomienda el bucle básico ya que no conoce la implementación de la lista.

Si eso era una lista de enlaces, cada llamada a

list.get(i)

Estaría iterando sobre la lista, resultando en una complejidad de tiempo N^2.

 19
Author: amarseillan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-08 19:22:19

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));
    }
}
 15
Author: eugene82,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-06 17:01:47

En Java 8 tenemos múltiples formas de iterar sobre las clases de colección.

Usando forEach iterable

Las colecciones que implementan Iterable (por ejemplo todas las listas) ahora tienen el método forEach. Podemos usar method-reference introducido en Java 8.

Arrays.asList(1,2,3,4).forEach(System.out::println);

Usando flujos forEach y forEachOrdered

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 donde as the forEachOrdered 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. Así que forEach no garantiza que el orden se mantendría.

La ventaja con los flujos es que también podemos hacer uso de flujos paralelos cuando sea apropiado. Si el objetivo es solo imprimir los elementos independientemente del orden, entonces podemos usar parallel stream como:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
 5
Author: i_am_zero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-25 02:08:09

No se lo que consideras patológico, pero déjame darte algunas alternativas que no podrías 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()));
}

También, 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.

 4
Author: Mario Rossi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-23 19:59:29

Puedes usar forEach a partir de Java 8:

 List<String> nameList   = new ArrayList<>(
            Arrays.asList("USA", "USSR", "UK"));

 nameList.forEach((v) -> System.out.println(v));
 2
Author: Sudip Bhandari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-23 17:28:03

Para una búsqueda hacia atrás debe utilizar 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, utilice el iterador.previousIndex (). También ayuda a escribir un bucle interno que compara dos posiciones en la lista (los iteradores no son iguales).

 0
Author: CoolMind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-25 13:12:18

En java 8 puede usar el método List.forEach() con 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);
                }
        );
    }
}
 0
Author: pippi longstocking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-06 13:34:26

Correcto, se enumeran muchas alternativas. Lo más fácil y limpio sería simplemente usar la sentencia enhanced for como se muestra a continuación. El Expression es de algún tipo que es iterable.

for ( FormalParameter : Expression ) Statement

Por ejemplo, para iterar a través de, Lista ids, podemos simplemente así,

for (String str : ids) {
    // Do something
}
 -1
Author: Yu Chen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-23 17:26:33

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 el do-while:

int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

Por supuesto, este tipo de cosas podrían causar una excepción NullPointerException si la lista.size () devuelve 0, porque siempre se ejecuta al menos una vez. Esto se puede arreglar probando si el elemento es null antes de usar sus atributos / métodos tho. Aún así, es mucho más simple y más fácil usar el bucle for

 -2
Author: shieldgenerator7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-23 19:07:36