Sätt att iterera över en lista i Java

Eftersom jag är ganska ny i Java-språket försöker jag bekanta mig med alla sätt (eller åtminstone de icke-patologiska) som man kan iterera genom en lista (eller kanske andra samlingar) och fördelarna eller nackdelarna med varje sätt.

Givet ett List list -objekt känner jag till följande sätt att slinga genom alla element:

Basic for loop (det finns naturligtvis även motsvarande "while"- och "do while"-slingor).

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

    // ...
}

Anmärkning: Som @amarseillan påpekade är denna form ett dåligt val. för att iterera över Lists, eftersom den faktiska implementeringen av metoden get kanske inte är lika effektiv som när man använder en Iterator. Till exempel måste implementeringar av LinkedList gå igenom hela element som föregår i för att få fram det i:e elementet.

I exemplet ovan finns det inget sätt för List-implementationen att "spara sin plats" för att göra framtida iterationer mer effektiva. För en ArrayList spelar det egentligen ingen roll, eftersom komplexiteten/kostnaden för get är konstant tid (O(1)) medan den för en LinkedList är proportionell mot listans storlek (O(n)).

Mer information om beräkningskomplexiteten hos de inbyggda Collections-implementationerna finns i denna fråga.

Förbättrad [for-slinga][for-each-slinga] (förklaras tydligt [i denna fråga][for-each-slinga-so])

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

Iteratoriterator

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

    // ...
}

Functional Java

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

Iterable.forEach, Stream.forEach, ...

(En map-metod från Java 8's Stream API (se @i_am_zero's svar).)

I Java 8 har samlingsklasser som implementerar Iterable (till exempel alla Lists) nu en forEach-metod, som kan användas istället för for loop statement som demonstreras ovan. (Här finns en annan fråga som ger en bra jämförelse.)

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

Vilka andra sätt finns det, om det finns några?

(Mitt intresse beror för övrigt inte alls på en önskan att [optimera prestanda][iterator-performance-question]; jag vill bara veta vilka former som är tillgängliga för mig som utvecklare.)

Exempel på varje typ som anges i frågan:

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());
     }
}
Kommentarer (0)

Jag vet inte vad du anser vara patologiskt, men låt mig ge dig några alternativ som du kanske inte har sett tidigare:

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

Eller dess rekursiva version:

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

Även en rekursiv version av det klassiska "for(int i=0...` :

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

Jag nämner dem eftersom du är "något ny i Java" och detta kan vara intressant.

Kommentarer (5)

Du kan alltid byta ut det första och tredje exemplet mot en while-slinga och lite mer kod. Detta ger dig fördelen att kunna använda do-while:

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

Naturligtvis kan den här typen av sak orsaka ett NullPointerException om list.size() returnerar 0, eftersom den alltid utförs minst en gång. Detta kan åtgärdas genom att testa om elementet är null innan dess attribut/metoder används tho. Det är ändå mycket enklare och lättare att använda for-slingan.

Kommentarer (3)