Autant que je sache, il existe deux approches:
Par exemple,
List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
// modify actual fooList
}
et
Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
// modify actual fooList using itr.remove()
}
Existe-t-il des raisons de préférer une approche à l’autre (par exemple, préférer la première à une simple raison de lisibilité)?
Laissez-moi vous donner quelques exemples avec quelques alternatives pour éviter une ConcurrentModificationException
.
Supposons que nous ayons la collection de livres suivante
List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
Collecter et supprimer
La première technique consiste à collecter tous les objets que vous souhaitez supprimer (par exemple, en utilisant une boucle for améliorée) et, une fois l'itération terminée, nous supprimons tous les objets trouvés.
ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
if(book.getIsbn().equals(isbn)){
found.add(book);
}
}
books.removeAll(found);
Ceci est supposé que l'opération que vous voulez faire est "supprimer".
Si vous voulez "ajouter" cette approche fonctionnerait aussi, mais je suppose que vous feriez une itération sur une collection différente pour déterminer les éléments que vous souhaitez ajouter à une seconde collection, puis utilisez une méthode addAll
à la fin.
Utilisation de ListIterator
Si vous travaillez avec des listes, une autre technique consiste à utiliser une variable ListIterator
qui prend en charge la suppression et l'ajout d'éléments pendant l'itération elle-même.
ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}
Encore une fois, j'ai utilisé la méthode "remove" dans l'exemple ci-dessus, ce que votre question semblait impliquer, mais vous pouvez également utiliser sa méthode add
pour ajouter de nouveaux éléments lors de l'itération.
Utilisation de JDK 8
Pour ceux qui travaillent avec Java 8 ou des versions supérieures, vous pouvez utiliser une ou deux autres techniques pour en tirer parti.
Vous pouvez utiliser la nouvelle méthode removeIf
dans la classe de base Collection
:
ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));
Ou utilisez la nouvelle API de flux:
ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
.filter(b -> b.getIsbn().equals(other))
.collect(Collectors.toList());
Dans ce dernier cas, pour filtrer des éléments d'une collection, vous réaffectez la référence d'origine à la collection filtrée (à savoir books = filtered
) ou utilisez la collection filtrée à removeAll
les éléments trouvés de la collection d'origine (à savoir books.removeAll(filtered)
).
Utilisez des sous-listes ou des sous-ensembles
Il y a aussi d'autres alternatives. Si la liste est triée et que vous souhaitez supprimer des éléments consécutifs, vous pouvez créer une sous-liste, puis l'effacer:
books.subList(0,5).clear();
Étant donné que la sous-liste est complétée par la liste d'origine, cela constituerait un moyen efficace de supprimer cette sous-collection d'éléments.
Quelque chose de similaire pourrait être obtenu avec des ensembles triés utilisant la méthode NavigableSet.subSet
ou l'une des méthodes de découpage proposées ici.
Considérations:
Quelle méthode vous utilisez peut dépendre de ce que vous avez l'intention de faire
removeAl
fonctionne avec n’importe quelle collection (Collection, Liste, Ensemble, etc.). ListIterator
ne fonctionne évidemment qu'avec des listes, à condition que leur implémentation ListIterator
offre la prise en charge des opérations d'ajout et de suppression. Iterator
fonctionnerait avec n'importe quel type de collection, mais elle ne prend en charge que les opérations de suppression.ListIterator
/Iterator
, l'avantage évident est que vous n'avez rien à copier car nous supprimons en itérant. Donc, c'est très efficace. removeAll
, l’inconvénient est qu’il faut effectuer une itération deux fois. Tout d'abord, nous parcourons la boucle du sol à la recherche d'un objet correspondant à nos critères de suppression, puis, une fois que nous l'avons trouvé, nous le demandons de le supprimer de la collection d'origine, ce qui impliquerait un second travail d'itération pour rechercher cet élément afin de: retirez-le. Iterator
est marquée comme "facultative" dans Javadocs, ce qui signifie qu'il pourrait y avoir des implémentations Iterator
qui jetteraient UnsupportedOperationException
si nous appelons la méthode remove. En tant que tel, je dirais que cette approche est moins sûre que d’autres si nous ne pouvons pas garantir le soutien de l’itérateur pour la suppression d’éléments.Y a-t-il des raisons de préférer une approche à l'autre?
La première approche fonctionnera, mais a la charge de travail évidente de copier la liste.
La seconde approche ne fonctionnera pas car beaucoup de conteneurs ne permettent pas de modification pendant l’itération. Ceci inclut ArrayList
.
Si la seule modification consiste à supprimer l'élément actuel, vous pouvez faire fonctionner la deuxième approche en utilisant itr.remove()
(c'est-à-dire, utilisez la méthode iterator 's remove()
, pas le conteneur ). Ce serait ma méthode préférée pour les itérateurs prenant en charge remove()
.
En Java 8, il existe une autre approche. Collection # removeIf
par exemple:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.removeIf(i -> i > 2);
Seule la deuxième approche fonctionnera. Vous pouvez modifier la collecte lors de l'itération en utilisant iterator.remove()
uniquement. Toutes les autres tentatives entraîneront ConcurrentModificationException
.
Vous ne pouvez pas faire la seconde, car même si vous utilisez la méthode remove()
sur Iterator , vous obtiendrez une exception levée .
Personnellement, je préférerais la première pour toutes les instances Collection
, malgré la découverte supplémentaire de la création de la nouvelle Collection
, je la trouve moins sujette aux erreurs lors de la modification par d'autres développeurs. Sur certaines implémentations de Collection, Iterator remove()
est pris en charge, sur d'autres, il ne l'est pas. Vous pouvez en lire plus dans la documentation pour Iterator .
La troisième alternative consiste à créer une nouvelle variable Collection
, à la parcourir par rapport à l'original et à ajouter tous les membres de la première variable Collection
à la seconde Collection
qui sont non à supprimer. En fonction de la taille de la variable Collection
et du nombre de suppressions, cela pourrait permettre d'économiser beaucoup de mémoire, par rapport à la première approche.
Je choisirais le second car vous n’auriez pas besoin de faire une copie de la mémoire et l’Iterator fonctionnera plus rapidement. Donc, vous économisez de la mémoire et du temps.
Il existe également une solution simple pour "itérer" une Collection
et supprimer chaque élément.
List<String> list = new ArrayList<>();
//Fill the list
Il conciste simplement sur le bouclage jusqu'à ce que la liste soit vide, et à chaque itération, nous supprimons le premier élément avec remove(0)
.
while(!list.isEmpty()){
String s = list.remove(0);
// do you thing
}
Je ne crois pas que cela apporte une amélioration par rapport à la variable Iterator
; il fallait toujours une liste modifiable, mais j'aime la simplicité de cette solution.