Spôsoby iterácie nad zoznamom v jazyku Java

Ako nováčik v jazyku Java sa snažím oboznámiť so všetkými spôsobmi (alebo aspoň s tými nepatologickými), ktorými možno iterovať cez zoznam (alebo možno iné kolekcie), a s výhodami či nevýhodami každého z nich.

Pri objekte List list poznám nasledujúce spôsoby prechádzania všetkých prvkov:

(samozrejme, existujú aj ekvivalentné slučky while / do 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

    // ...
}

Poznámka: Ako upozornil @amarseillan, tento tvar je zlou voľbou pre iteráciu cez List, pretože skutočná implementácia metódy get nemusí byť taká efektívna ako pri použití Iterátora. Napríklad implementácie LinkedList musia prechádzať všetky prvkami predchádzajúcimi prvku i, aby sa získal i-ty prvok.

Vo vyššie uvedenom príklade neexistuje spôsob, ako by implementácia List "uložiť svoje miesto", aby boli budúce iterácie efektívnejšie. Pre ArrayList na tom v skutočnosti nezáleží, pretože zložitosť/náklady na get sú konštantné (O(1)), zatiaľ čo pre LinkedList sú úmerné veľkosti zoznamu (O(n)).

Viac informácií o výpočtovej zložitosti zabudovaných implementácií Collections nájdete v táto otázka.

Vylepšená slučka for (pekne vysvetlené v tejto otázke)

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

    // ...
}

Iterátor

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][list-iterátor]

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

    // ...
}

Funkčná Java

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

Iterable.forEach, Stream.forEach, ...

(Metóda mapovania z Java 8's Stream API (pozri odpoveď @i_am_zero's).)

V Jave 8 majú triedy kolekcií, ktoré implementujú Iterable (napríklad všetky Listy), teraz metódu forEach, ktorú možno použiť namiesto vyššie demonštrovaného príkazu for-loop. (Tu je iná otázka, ktorá poskytuje dobré porovnanie.)

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

Aké sú ďalšie spôsoby, ak nejaké existujú?

(BTW, môj záujem vôbec nevyplýva z túžby optimalizovať výkon; chcem len vedieť, aké formy mám ako vývojár k dispozícii.)

Príklad každého druhu uvedeného v otázke:

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());
     }
}
Komentáre (0)

Neviem, čo považujete za patologické, ale dovoľte mi poskytnúť niekoľko alternatív, ktoré ste doteraz nemohli vidieť:

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

Alebo jeho rekurzívna verzia:

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

Tiež rekurzívna verzia klasického for(int i=0... :

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

Spomínam ich preto, lebo ste "trochu nováčik v Jave" a toto by mohlo byť zaujímavé.

Komentáre (5)

Prvý a tretí príklad môžete vždy vymeniť za cyklus while a trochu viac kódu. Tým získate výhodu, že budete môcť používať do-while:

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

Samozrejme, takáto vec môže spôsobiť výnimku NullPointerException, ak list.size() vráti 0, pretože sa vždy vykoná aspoň raz. Toto sa dá vyriešiť testovaním, či je prvok nulový pred použitím jeho atribútov/metód. Napriek tomu je oveľa jednoduchšie a ľahšie použiť cyklus for

Komentáre (3)