Modi per iterare su un elenco in Java


Essendo un po ' nuovo al linguaggio Java sto cercando di familiarizzare con tutti i modi (o almeno quelli non patologici) che si potrebbero scorrere attraverso una lista (o forse altre collezioni) e i vantaggi o gli svantaggi di ciascuno.

Dato un oggetto List<E> list, conosco i seguenti modi per scorrere tutti gli elementi:

Di base per loop (ovviamente, ci sono equivalenti while / do while loop pure)

// 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: Come @ amarseillan ha sottolineato, questa forma è una scelta sbagliata per iterare su List s, perché l'effettiva implementazione di il metodo get potrebbe non essere efficiente come quando si utilizza un Iterator. Ad esempio, le implementazioni LinkedList devono attraversare tutti gli elementi che precedono i per ottenere l'elemento i-esimo.

Nell'esempio precedente non c'è modo per l'implementazione List di "salva il suo posto" per rendere le iterazioni future più efficienti. Per un ArrayList non ha molta importanza, perché il complessità / costo di get è tempo costante (O(1)) mentre per un LinkedList è proporzionale alla dimensione della lista (O(n)).

Per ulteriori informazioni sulla complessità computazionale delle implementazioni Collectionsincorporate, controlla questa domanda.

Enhanced per il ciclo (ben spiegato in questa domanda )

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

    // ...
}

Iteratore

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

    // ...
}

Funzionale Java

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

Iterabile.forEach, Flusso.forEach, ...

(Un metodo map dall'API Stream di Java 8 (vedi la risposta di @i_am_zero).)

Nelle classi di raccolta Java 8 che implementano Iterable (ad esempio, tutte le List hanno ora un metodo forEach, che può essere utilizzato al posto dell'istruzione for loop dimostrata sopra. (Ecco un'altra domanda che fornisce un buon confronto.)

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

Quali altri modi ci sono, se qualsiasi?

(A proposito, il mio interesse non deriva affatto dal desiderio di ottimizzare le prestazioni; Voglio solo sapere quali forme sono disponibili per me come sviluppatore.)

Author: Peter Mortensen, 2013-08-23

11 answers

Le tre forme di looping sono quasi identiche. Il ciclo for migliorato:

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

È, secondo la specifica del linguaggio Java , identico in effetti all'uso esplicito di un iteratore con un ciclo tradizionale for. Nel terzo caso, è possibile modificare il contenuto dell'elenco solo rimuovendo l'elemento corrente e solo se lo si fa tramite il metodo remove dell'iteratore stesso. Con l'iterazione basata su indice, sei libero di modificare l'elenco in qualsiasi modo. Tuttavia, l'aggiunta o la rimozione di elementi che precedono l'indice corrente rischia di far saltare gli elementi del ciclo o di elaborare lo stesso elemento più volte; è necessario regolare correttamente l'indice del ciclo quando si apportano tali modifiche.

In tutti i casi, element è un riferimento all'elemento elenco effettivo. Nessuno dei metodi di iterazione crea una copia di qualsiasi cosa nell'elenco. Le modifiche allo stato interno di element saranno sempre visualizzate nello stato interno dell'elemento corrispondente sul elenco.

Essenzialmente, ci sono solo due modi per iterare su un elenco: usando un indice o usando un iteratore. L'enhanced for loop è solo una scorciatoia sintattica introdotta in Java 5 per evitare la noia di definire esplicitamente un iteratore. Per entrambi gli stili, puoi trovare variazioni essenzialmente banali usando for, while o do while blocchi, ma tutti si riducono alla stessa cosa (o, piuttosto, due cose).

EDIT: Come sottolinea @iX3 in un commento, puoi usare un ListIterator per impostare l'elemento corrente di un elenco mentre si sta iterando. Si avrebbe bisogno di usare List#listIterator() invece di List#iterator() per inizializzare la variabile loop (che, ovviamente, dovrebbe essere dichiarata a ListIterator piuttosto che a Iterator).

 215
Author: Ted Hopp, 2013-12-15 21:01:52

Esempio di ogni tipo elencato nella domanda:

ListIterationExample.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, 2013-08-23 21:09:38

Il ciclo di base non è raccomandato in quanto non si conosce l'implementazione dell'elenco.

Se quello era un LinkedList, ogni chiamata a

list.get(i)

Sarebbe l'iterazione sulla lista, con conseguente complessità temporale N^2.

 19
Author: amarseillan, 2015-04-08 19:22:19

Un'iterazione in stile 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, 2014-04-06 17:01:47

In Java 8 abbiamo diversi modi per iterare sulle classi di raccolta.

Utilizzando forEach iterabile

Le raccolte che implementano Iterable (ad esempio tutte le liste) ora hanno il metodo forEach. Possiamo usare riferimento al metodo introdotto in Java 8.

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

Utilizzo dei flussi forEach e forEachOrdered

Possiamo anche iterare su un elenco usando Stream come:

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

Dovremmo preferire forEachOrdered su forEach perché il comportamento di forEach è esplicitamente non deterministico dove come forEachOrdered esegue un'azione per ogni elemento di questo flusso, nell'ordine di incontro del flusso se il flusso ha un ordine di incontro definito. Così forEach non garantisce che l'ordine sarebbe mantenuto.

Il vantaggio con i flussi è che possiamo anche fare uso di flussi paralleli laddove appropriato. Se l'obiettivo è solo quello di stampare gli articoli indipendentemente dall'ordine, possiamo usare parallel stream come:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
 5
Author: i_am_zero, 2018-02-25 02:08:09

Non lo so cosa consideri patologico, ma lascia che ti fornisca alcune alternative che potresti non aver visto prima:

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

O la sua versione ricorsiva:

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

Inoltre, una versione ricorsiva del classico 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);
}

Li menziono perché sei "un po' nuovo a Java" e questo potrebbe essere interessante.

 4
Author: Mario Rossi, 2013-08-23 19:59:29

Puoi usare forEach a partire da Java 8:

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

 nameList.forEach((v) -> System.out.println(v));
 2
Author: Sudip Bhandari, 2018-02-23 17:28:03

Per una ricerca all'indietro dovresti usare quanto segue:

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

Se vuoi conoscere una posizione, usa iterator.previousIndex (). Aiuta anche a scrivere un ciclo interno che confronta due posizioni nell'elenco (gli iteratori non sono uguali).

 0
Author: CoolMind, 2016-11-25 13:12:18

In java 8 è possibile utilizzare il metodo List.forEach() con lambda expression per iterare su un elenco.

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, 2018-09-06 13:34:26

Giusto, sono elencate molte alternative. Il più semplice e pulito sarebbe solo usare l'istruzione enhanced for come di seguito. Il Expression è di qualche tipo che è iterabile.

for ( FormalParameter : Expression ) Statement

Ad esempio, per scorrere, List id, possiamo semplicemente così,

for (String str : ids) {
    // Do something
}
 -1
Author: Yu Chen, 2018-02-23 17:26:33

Puoi sempre cambiare il primo e il terzo esempio con un ciclo while e un po ' più di codice. Questo ti dà il vantaggio di essere in grado di utilizzare il do-while:

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

Naturalmente, questo genere di cose potrebbe causare un NullPointerException se l'elenco.size () restituisce 0, perché viene sempre eseguito almeno una volta. Questo può essere risolto testando se l'elemento è nullo prima di utilizzare i suoi attributi / metodi tho. Tuttavia, è molto più semplice e più facile usare il ciclo for

 -2
Author: shieldgenerator7, 2013-08-23 19:07:36