Question Façons d'itérer sur une liste en Java


Étant quelque peu nouveau dans le langage Java, j'essaie de me familiariser avec tous les moyens (ou du moins les aspects non pathologiques) que l'on peut parcourir dans une liste (ou peut-être d'autres collections) et leurs avantages ou inconvénients.

Donné un List<E> list objet, je connais les moyens suivants pour parcourir tous les éléments:

De base pour  boucle (bien sûr, il y a l'équivalent while / do while boucles aussi bien)

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

    // ...
}

Note: Comme @amarseillan l'a souligné, cette forme est un mauvais choix pour itérer sur Lists, parce que la mise en œuvre effective de la get méthode peut ne pas être aussi efficace que lors de l'utilisation d'un Iterator. Par exemple, LinkedList les implémentations doivent traverser tous les éléments précédant i pour obtenir le i-ème élément.

Dans l'exemple ci-dessus, il n'y a aucun moyen pour le List mise en œuvre "Sauver sa place" pour rendre les futures itérations plus efficaces. Pour un ArrayList cela n'a pas vraiment d'importance, car la complexité / coût de get est le temps constant (O (1)) alors que pour un LinkedList est-il proportionnel à la taille de la liste (O (n)).

Pour plus d'informations sur la complexité de calcul de l'intégré Collections implémentations, consultez cette question.

Renforcée pour la boucle (bien expliqué dans cette question)

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

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 fonctionnel

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

Iterable.forEach, Stream.forEach, ...

(Une méthode cartographique de l'API Stream de Java 8 (voir la réponse de @ i_am_zero).)

Dans les classes de collection Java 8 qui implémentent Iterable (par exemple, tous Lists) ont maintenant un forEach méthode, qui peut être utilisé à la place du pour l'énoncé de boucle démontré ci-dessus. (Voici une autre question cela fournit une bonne comparaison.)

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

Quels autres moyens y a-t-il, le cas échéant?

(BTW, mon intérêt ne provient pas du tout d'un désir de optimiser les performances; Je veux juste savoir quels formulaires sont disponibles pour moi en tant que développeur.)


445
2017-08-23 19:00


origine


Réponses:


Les trois formes de bouclage sont presque identiques. Le amélioré for boucle:

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

est, selon le Spécification du langage Java, identique en effet à l'utilisation explicite d'un itérateur avec un traditionnel for boucle. Dans le troisième cas, vous ne pouvez modifier le contenu de la liste qu'en supprimant l'élément actuel, et uniquement si vous le faites via le remove méthode de l'itérateur lui-même. Avec l'itération par index, vous êtes libre de modifier la liste de quelque manière que ce soit. Cependant, l'ajout ou la suppression d'éléments précédant l'index en cours risque de faire perdre à votre boucle des éléments ou de traiter le même élément plusieurs fois. vous devez ajuster l'index de boucle correctement lorsque vous effectuez de tels changements.

Dans tous les cas, element est une référence à l'élément de liste réel. Aucune des méthodes d'itération ne fait une copie de quelque chose dans la liste. Changements à l'état interne de elementsera toujours vu dans l'état interne de l'élément correspondant sur la liste.

Essentiellement, il n'y a que deux façons d'itérer sur une liste: en utilisant un index ou en utilisant un itérateur. La boucle for enhanced est juste un raccourci syntaxique introduit dans Java 5 pour éviter l'ennui de la définition explicite d'un itérateur. Pour les deux styles, vous pouvez proposer des variations essentiellement triviales en utilisant for, while ou do while blocs, mais ils se résument à la même chose (ou plutôt à deux choses).

EDIT: Comme @ iX3 souligne dans un commentaire, vous pouvez utiliser un ListIterator pour définir l'élément actuel d'une liste pendant que vous itérez. Vous auriez besoin d'utiliser List#listIterator() au lieu de List#iterator() pour initialiser la variable de boucle (qui, évidemment, devrait être déclarée ListIterator plutôt qu'un Iterator).


205
2017-08-23 19:29



Exemple de chaque type répertorié dans la question:

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
2017-08-23 19:22



La boucle de base n'est pas recommandée car vous ne connaissez pas l'implémentation de la liste.

Si c'était une LinkedList, chaque appel à

list.get(i)

serait itérer sur la liste, résultant dans la complexité de temps N ^ 2.


19
2018-04-08 19:22



Une itération de style 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
2018-04-06 17:01



Je ne sais pas ce que vous considérez comme pathologique, mais laissez-moi vous fournir quelques alternatives que vous n'auriez jamais pu voir auparavant:

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

Ou sa version récursive:

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

Aussi, une version récursive du classique 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);
}

Je les mentionne parce que vous êtes "un peu nouveau à Java" et cela pourrait être intéressant.


4
2017-08-23 19:18



Dans Java 8 Nous avons plusieurs façons d'itérer sur les classes de collection.

Utiliser Iterable forEach

Les collections qui mettent en œuvre Iterable (par exemple toutes les listes) ont maintenant forEach méthode. On peut utiliser méthode-référence introduit dans Java 8.

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

Utilisation de Streams forEach et forEachOrdered

Nous pouvons également parcourir une liste en utilisant Courant comme:

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

Nous devrions préférer forEachOrdered plus de forEach parce que le comportement de forEach est explicitement non déterministe où, comme le forEachOrdered effectue une action pour chaque élément de ce flux, dans l'ordre de rencontre du flux si le flux a un ordre de rencontre défini. Donc forEach ne garantit pas que la commande serait conservée.

L'avantage des flux est que nous pouvons également utiliser des flux parallèles, le cas échéant. Si l'objectif est uniquement d'imprimer les articles indépendamment de la commande, nous pouvons utiliser le flux parallèle comme:

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

3
2018-05-24 09:38



Vous pouvez utiliser pour chaque à partir de Java 8:

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

 nameList.forEach((v) -> System.out.println(v));

2
2018-01-22 10:51