Formas de iterar sobre una lista en Java

Resuelto jacobq asked hace 11 años • 13 respuestas

Siendo algo nuevo en el lenguaje Java, estoy tratando de familiarizarme con todas las formas (o al menos las no patológicas) en las que uno puede recorrer una lista (o quizás otras colecciones) y las ventajas o desventajas de cada una.

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

Bucle for básico (por supuesto, también hay bucles / equivalentes)whiledo 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, esta forma es una mala elección para iterar sobre Lists, porque la implementación real del getmétodo puede no ser tan eficiente como cuando se usa un Iterator. Por ejemplo, LinkedListlas implementaciones deben atravesar todos los elementos que preceden a i para obtener el i-ésimo elemento.

En el ejemplo anterior, no hay forma de que la Listimplementación "guarde su lugar" para hacer que las iteraciones futuras sean más eficientes. Para an ArrayListrealmente no importa, porque la complejidad/costo de getes tiempo constante (O(1)) mientras que para a LinkedListes 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 .

Bucle for mejorado (muy 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

    // ...
}

ListaIterador

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 Stream API de Java 8 (consulte la respuesta de @i_am_zero).)

En Java 8, las clases de colección que implementan Iterable(por ejemplo, todas Listlas s) ahora tienen un forEachmétodo, que se puede utilizar en lugar de la instrucción de bucle 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 existen, si las hay?

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

jacobq avatar Aug 24 '13 02:08 jacobq
Aceptado

Las tres formas de bucle son casi idénticas. El bucle mejorado for:

for (E element : list) {
    . . .
}

es, según la Especificación del lenguaje Java , idéntico en efecto al uso explícito de un iterador con un forbucle tradicional. En el tercer caso, sólo puedes modificar el contenido de la lista eliminando el elemento actual y, entonces, sólo si lo haces a través del removemétodo del propio iterador. Con la iteración basada en índices, usted es libre de modificar la lista de cualquier forma. Sin embargo, agregar o eliminar elementos que vienen antes del índice actual corre el riesgo de que el 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, elementes una referencia al elemento de la 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 elementsiempre se verán en el estado interno del elemento correspondiente en la lista.

Básicamente, sólo hay dos formas de iterar sobre una lista: utilizando un índice o utilizando 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, whileo do whilebloques, pero todos se reducen a lo mismo (o, mejor dicho, dos cosas).

EDITAR: Como @iX3 señala en un comentario, puede usar a ListIteratorpara establecer el elemento actual de una lista mientras itera. Necesitaría usar List#listIterator()en lugar de List#iterator()para inicializar la variable de bucle (que, obviamente, tendría que declararse como ListIteratoren lugar de como Iterator).

Ted Hopp avatar Aug 23 '2013 19:08 Ted Hopp

Ejemplo de cada tipo enumerado en la pregunta:

ListaIteraciónEjemplo.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());
     }
}
jacobq avatar Aug 23 '2013 19:08 jacobq

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

Si se tratara de una LinkedList, cada llamada a

list.get(i)

estaría iterando sobre la lista, lo que resultaría en una complejidad de tiempo N^2.

amarseillan avatar Apr 08 '2015 19:04 amarseillan