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


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

Étant donné un objet List<E> list, je connais les façons suivantes de parcourir tous les éléments:

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

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

    // ...
}

Remarque: Comme @amarseillan a souligné, ce formulaire est un mauvais choix pour itérer sur List s, car l'implémentation réelle de la méthode get peut ne pas être aussi efficace que lors de l'utilisation d'un Iterator. Par exemple, les implémentations LinkedList doivent traverser les éléments précédents i pour obtenir le i-ème élément.

Dans l'exemple ci-dessus, il n'y a aucun moyen pour l'implémentation List de "sauvez sa place" pour rendre les itérations futures plus efficaces. Pour un ArrayList, cela n'a pas vraiment d'importance, car le la complexité / coût de get est un 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 Collections implémentations, découvrez cette question.

Améliorée pour la boucle (gentiment expliqué , dans cette question, )

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

    // ...
}

Itérateur

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

    // ...
}

Fonctionnel Java

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

Itérable.forEach, Flux de données.forEach, ...

(Une méthode map 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 les List s) ont maintenant une méthode forEach, qui peut être utilisée à la place de l'instruction for loop démontrée ci-dessus. (Voici une autre question qui 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, si tout?

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

Author: Peter Mortensen, 2013-08-23

11 answers

Les trois formes de looping sont presque identiques. La boucle for améliorée:

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

Est, selon la spécification du langage Java , identique en effet à l'utilisation explicite d'un itérateur avec une boucle for traditionnelle. Dans le troisième cas, vous ne pouvez modifier le contenu de la liste qu'en supprimant l'élément actuel, et seulement si vous le faites via la méthode remove de l'itérateur lui-même. Avec l'itération basée sur un index, vous êtes libre de modifier la liste dans n'importe quel façon. Cependant, l'ajout ou la suppression d'éléments qui précèdent l'index actuel risque de voir votre boucle sauter des éléments ou traiter le même élément plusieurs fois; vous devez ajuster correctement l'index de boucle lorsque vous effectuez de telles modifications.

Dans tous les cas, element est une référence à l'élément de la liste. Aucune des méthodes d'itération ne fait une copie de quoi que ce soit dans la liste. Les changements de l'état interne de element sera toujours vu dans l'état interne de l'élément correspondant de 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 améliorée n'est qu'un raccourci syntaxique introduit dans Java 5 pour éviter l'ennui de définir explicitement un itérateur. Pour les deux styles, vous pouvez trouver des variations essentiellement triviales en utilisant for, while ou do while blocs, mais ils se résument tous à 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 courant de la liste que vous l'itération. Vous devez utiliser List#listIterator() au lieu de List#iterator() pour initialiser la variable de boucle (qui, de toute évidence, elle aurait dû être déclarée ListIterator plutôt que Iterator).

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

Exemple de chaque type énuméré 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
Author: iX3, 2013-08-23 21:09:38

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

Si c'était une liste de liens, chaque appel à

list.get(i)

Itérerait sur la liste, ce qui entraînerait une complexité temporelle N^2.

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

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
Author: eugene82, 2014-04-06 17:01:47

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

Utilisation de forEach itérable

Les collections qui implémentent Iterable (par exemple toutes les listes) ont maintenant la méthode forEach. Nous pouvons utiliser méthode de référence introduit dans Java 8.

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

Utilisation des flux forEach et forEachOrdered

Nous pouvons également itérer sur une liste en utilisant Stream 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 à forEach parce que le comportement de forEach est explicitement non déterministe lorsque 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 avec les flux est que nous pouvons également utiliser des flux parallèles lorsque cela est approprié. Si l'objectif est uniquement d'imprimer les éléments indépendamment de l'ordre alors nous pouvons utiliser en parallèle flux:

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

Je ne sais pas ce que vous considérez comme pathologique, mais permettez-moi de fournir quelques alternatives que vous n'auriez pas 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 sur Java" et cela pourrait être intéressant.

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

Vous pouvez utiliser forEach à partir de 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

Pour une recherche en arrière, vous devez utiliser ce qui suit:

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

Si vous voulez connaître une position, utilisez iterator.previousIndex (). Il est également utile d'écrire une boucle interne qui compare deux positions dans la liste (les itérateurs ne sont pas égaux).

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

Dans java 8 vous pouvez utiliser List.forEach() méthode avec lambda expression pour itérer sur une liste.

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

À droite, de nombreuses alternatives sont énumérées. Le plus simple et le plus propre serait simplement d'utiliser l'instruction enhanced for comme ci-dessous. Le Expression est d'un type qui est itératif.

for ( FormalParameter : Expression ) Statement

Par exemple, pour parcourir la liste des ID , nous pouvons simplement le faire,

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

Vous pouvez toujours changer les premier et troisième exemples avec une boucle while et un peu plus de code. Cela vous donne l'avantage de pouvoir utiliser le do-while:

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

Bien sûr, ce genre de chose pourrait provoquer une NullPointerException si la liste.size () renvoie 0, car il est toujours exécuté au moins une fois. Cela peut être corrigé en testant si l'élément est null avant d'utiliser ses attributs / méthodes tho. Pourtant, il est beaucoup plus simple et plus facile d'utiliser la boucle for

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