É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:
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 List
s, 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 .
for (E element : list) {
// 1 - can call methods of element
// ...
}
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
// ...
}
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
// ...
}
list.stream().map(e -> e + 1); // Can apply a transformation function for e
(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 List
s) 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.)
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
).
Exemple de chaque type énuméré dans la question:
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());
}
}
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.
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));
}
}
En Java 8 , nous avons plusieurs façons d’itérer sur des classes de collection.
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);
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);
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.
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));
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).
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);
}
);
}
}