Façons d'itérer une liste en Java
étant un peu nouveau dans le langage Java, j'essaie de me familiariser avec toutes les façons (ou au moins les non-pathologiques) qu'on pourrait itérer à travers une liste (ou peut-être d'autres collections) et les avantages ou inconvénients de chacune.
avec un objet List<E> list
, je connais les façons suivantes de boucler tous les éléments:
de 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
// ...
}
Note: comme @amarseillan l'a souligné, ce formulaire est un mauvais choix
pour itérer plus de List
s, parce que la mise en œuvre effective de
la méthode get
n'est peut-être pas aussi efficace que la méthode Iterator
.
Par exemple, les implémentations LinkedList
doivent traverser toutes les
les éléments qui précèdent i pour obtenir l'élément i-ème.
Dans l'exemple ci-dessus il y a la mise en œuvre de List
ne peut en aucun cas
"sauvegarder sa place" pour rendre les itérations futures plus efficaces.
Pour un ArrayList
cela n'a pas vraiment d'importance, parce que la complexité/coût de get
est temps constant (O(1)) tandis que pour un LinkedList
est proportionnel à la taille de la liste (O(n)).
pour plus d'information sur la complexité computationnelle des implémentations Collections
, consultez cette question .
Renforcé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.forEach ,...
(une méthode de carte à partir de L'API Stream de Java 8 (voir la réponse de @i_am_zero).)
en Java 8 Les classes de collection qui implémentent Iterable
(par exemple, toutes les List
s) ont maintenant une méthode forEach
, qui peut être utilisée à la place du pour l'énoncé de boucle démontré 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).
y a-t-il d'autres moyens, le cas échéant?
(BTW, mon intérêt ne découle pas du tout d'un désir de optimiser la performance ; je veux juste savoir quelles formes sont disponibles pour moi en tant que développeur.)
11 réponses
les trois formes de boucle sont presque identiques. La boucle améliorée for
:
for (E element : list) {
. . .
}
est, selon le Java Language Specification , à l'identique , en effet, à l'utilisation explicite d'un itérateur avec un traditionnel for
en boucle. Dans le troisième cas, vous ne pouvez modifier le contenu de la liste qu'en supprimant l'élément courant, et seulement si vous le faites par la méthode remove
de la itérateur lui-même. Avec l'indice de l'itération, vous êtes libre de modifier la liste en aucune façon. Cependant, l'ajout ou la suppression d'éléments qui précèdent l'index courant risque de faire sauter des éléments de votre boucle ou de traiter le même élément plusieurs fois; vous devez ajuster l'index de boucle correctement lorsque vous apportez de telles modifications.
dans tous les cas, element
renvoie à l'élément de la liste. Aucune des méthodes d'itération fait une copie de n'importe quoi 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 une liste: en utilisant un index ou un itérateur. La boucle enhanced for n'est qu'un raccourci syntaxique introduit en 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 elles se résument toutes à la même chose (ou, plutôt, de deux choses).
EDIT: @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, évidemment, devrait être déclaré un ListIterator
plutôt qu'un Iterator
).
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());
}
}
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)
serait itératif sur la liste, résultant en n^2 complexité de temps.
a JDK8-style itération:
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));
}
}
dans Java 8 nous avons plusieurs façons d'itérer sur les classes de collecte.
à l'Aide de Itérable forEach
les collections qui implémentent Iterable
(par exemple toutes les listes) ont maintenant la méthode forEach
. Nous pouvons utiliser méthode-référence introduit en Java 8.
Arrays.asList(1,2,3,4).forEach(System.out::println);
à l'Aide de Flux de forEach et forEachOrdered
nous pouvons aussi itérer sur une liste à l'aide de Flux :
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 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. Ainsi forEach ne garantit pas que la commande serait maintenue.
L'avantage avec streams est que nous pouvons également utiliser des streams 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 courant parallèle:
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 fournir quelques alternatives que vous n'auriez pas pu voir avant:
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 en 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, 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 (). Cela aide aussi à écrire une boucle interne qui compare deux positions dans la liste (les itérateurs ne sont pas égaux).
Dans java 8
vous pouvez utiliser List.forEach()
méthode 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);
}
);
}
}
D'accord, de nombreuses alternatives sont énumérées. Le plus facile et le plus propre serait d'utiliser simplement l'énoncé amélioré for
comme ci-dessous. Le Expression
est d'un certain type qui est itérable.
for ( FormalParameter : Expression ) Statement
par exemple, pour itérer, List
for (String str : ids) {
// Do something
}
vous pouvez toujours désactiver les premier et troisième exemples avec une boucle de temps 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 causer une NullPointerException si la liste.size () renvoie 0, parce qu'il est toujours exécuté au moins une fois. Cela peut être corrigé en testant si l'élément est nul avant d'utiliser ses attributs / méthodes tho. Pourtant, il est beaucoup plus simple et plus facile à utiliser la boucle pour