Maneiras de iterar sobre uma lista em Java

Sendo algo novo na linguagem Java I'estou tentando me familiarizar com todas as formas (ou pelo menos as não-patológicas) que se pode iterar através de uma lista (ou talvez outras coleções) e as vantagens ou desvantagens de cada uma.

Dado um List list object, eu conheço as seguintes formas de fazer loop através de todos os elementos:

Basic for loop (claro, lá're equivalente while / do while loops as well)

// 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 assinalou, esta forma é uma má escolha para iterar sobre a "Lista", porque a implementação real do o método 'get' pode não ser tão eficiente como quando se utiliza um 'Iterator'. Por exemplo, implementações de LinkedList devem atravessar todas as os elementos que precedem o i para obter o i-ésimo elemento.

No exemplo acima não há's nenhuma maneira para a implementação da Lista de "salvar seu lugar" para tornar as futuras iterações mais eficientes. Para uma ArrayList não importa'porque a complexidade/custo do get é tempo constante (O(1)) enquanto que para uma LinkedList é proporcional ao tamanho da lista (O(n)).

Para mais informações sobre a complexidade computacional das implementações integradas de `Collections', confira esta pergunta.

Enhanced [for loop][for-each-loop-soop] (bem explicado nesta pergunta)

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]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

    // ...
}

Functional Java functional-java

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach]iterable-forEach, [Stream.forEach]stream.forEach, ...

(Um método de mapa do Java 8's Stream API (ver @i_am_zero's resposta)).

Em classes de coleção Java 8 que implementam Iterable (por exemplo, todas as Lists) agora têm um método forEach, que pode ser utilizado ao invés do for loop statement demonstrado acima. (Aqui está outra questão que fornece uma boa comparação).

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).

Que outras formas existem, se é que existem?

(BTW, o meu interesse não deriva em absoluto de um desejo de otimizar o desempenho; eu só quero saber que formas estão disponíveis para mim como desenvolvedor).

Exemplo de cada tipo listado na pergunta:

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List numbers = new ArrayList();

        // 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 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 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 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 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 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());
     }
}
Comentários (0)

Eu não'não sei o que você considera patológico, mas deixe-me fornecer algumas alternativas que você não poderia ter visto antes:

List sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

Ou a sua versão recursiva:

void visit(List list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

Também, uma versão recursiva do clássico for(int i=0... :

void visit(List list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

Eu os menciono porque você é " um pouco novo no Java" e isso pode ser interessante.

Comentários (5)

Você sempre poderia trocar o primeiro e terceiro exemplos com um laço de tempo e um pouco mais de código. Isto dá-lhe a vantagem de poder usar o do-while:

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

Claro, este tipo de coisa pode causar uma NullPointerException se o list.size() retornar 0, porque ele sempre é executado pelo menos uma vez. Isto pode ser corrigido testando se o elemento é nulo antes de usar seus atributos / métodos tho. Ainda assim, it's é muito mais simples e fácil de usar o loop for

Comentários (3)