web-dev-qa-db-fra.com

Méthodes d'itération sur une liste dans Java

Étant un peu nouveau dans le langage Java, j'essaie de me familiariser avec tous les moyens (ou du moins ceux qui ne sont pas pathologiques) de parcourir une liste (ou peut-être d'autres collections) et les avantages ou des inconvénients de chacun.

Avec un objet List<E> list, je connais les méthodes suivantes pour parcourir tous les éléments:

Basic pourboucle (bien sûr, il y a des boucles équivalentes while/do while 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 l'a souligné @amarseillan, cette forme est un mauvais choix pour effectuer une itération sur Lists, car la mise en œuvre réelle de la méthode get peut ne pas être aussi efficace que lors de l'utilisation de Iterator. Par exemple, les implémentations LinkedList doivent traverser tous les éléments précédents pour obtenir le i-ème élément.

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

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

Amélioré pour la boucle (bien 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

    // ...
}

Java fonctionnel

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

Iterable.forEach , Stream.forEach , ...

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

Dans Java 8, les classes de collection implémentant Iterable (par exemple, toutes les Lists) ont maintenant une méthode forEach, qui peut être utilisée à la place de la méthode pour l'instruction de boucle démontré ci-dessus. (Voici ne 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 sont les autres moyens, le cas échéant?

(En passant, mon intérêt ne provient pas du tout du désir de optimiser les performances ; je veux juste savoir quelles formes sont disponibles pour moi en tant que développeur.)

532
iX3

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

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

est, selon la spécification de langage Java , identique à l'utilisation explicite d'un itérateur avec une boucle for traditionnelle. Dans le troisième cas, vous pouvez uniquement modifier le contenu de la liste en supprimant l'élément en cours, puis uniquement si vous le faites via la méthode remove de l'itérateur lui-même. Avec l'itération basée sur l'index, vous êtes libre de modifier la liste de quelque manière que ce soit. Toutefois, l'ajout ou la suppression d'éléments précédant l'index actuel risque de faire en sorte que votre boucle omette des éléments ou traite 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 liste réel. Aucune des méthodes d'itération ne copie de quoi que ce soit dans la liste. Les modifications apportées à l'état interne de element seront toujours visibles dans l'état interne de l'élément correspondant de la liste.

En gros, il n’ya que deux façons de parcourir 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 la définition explicite d’un itérateur. Pour les deux styles, vous pouvez créer des variations essentiellement triviales en utilisant les blocs for, while ou do while, mais ils se résument tous à la même chose (ou plutôt à deux choses).

EDIT: Comme @ iX3 le souligne dans un commentaire, vous pouvez utiliser un ListIterator pour définir l’élément actuel d’une liste au fur et à mesure de votre itération. 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 au lieu de Iterator).

245
Ted Hopp

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());
     }
}
43
iX3

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

S'il s'agissait d'une LinkedList, chaque appel de

list.get(i)

itérer sur la liste, ce qui entraînerait une complexité de temps N ^ 2.

20
amarseillan

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));
    }
}
19
eugene82

En Java 8 , nous avons plusieurs façons d’itérer sur des classes de collection.

Utiliser Iterable pour chaque

Les collections qui implémentent Iterable (par exemple toutes les listes) ont maintenant la méthode forEach. Nous pouvons utiliser method-reference 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 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 car le comportement de forEach est explicitement non déterministe, car, comme forEachOrdered exécute une action pour chaque élément de ce flux, dans l'ordre de rencontre des stream si le flux a un ordre de rencontre défini. ForEach ne garantit donc pas que la commande sera 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 éléments indépendamment de la commande, nous pouvons utiliser le flux parallèle comme:

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

Je ne sais pas ce que vous considérez comme pathologique, mais laissez-moi vous proposer des alternatives que vous n'auriez jamais vues 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()));
}

En outre, 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
Mario Rossi

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
Sudip Bhandari

Pour une recherche en arrière, utilisez les éléments suivants:

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
CoolMind

Dans Java 8, vous pouvez utiliser la méthode List.forEach() avec lambda expression pour parcourir 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
pippi longstocking