Nous savons tous que vous ne pouvez pas faire cela:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
ConcurrentModificationException
etc ... cela semble fonctionner parfois, mais pas toujours. Voici un code spécifique:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Bien entendu, cela se traduit par:
Exception in thread "main" Java.util.ConcurrentModificationException
... même si plusieurs threads ne le font pas ... Quoi qu'il en soit.
Quelle est la meilleure solution à ce problème? Comment puis-je supprimer un article de la collection dans une boucle sans lancer cette exception?
J'utilise également une variable Collection
ici, pas nécessairement une ArrayList
, de sorte que vous ne pouvez pas compter sur get
.
Iterator.remove()
est sans danger, vous pouvez l'utiliser comme ceci:
_List<String> list = new ArrayList<>();
// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
// Remove the current element from the iterator and the list.
iterator.remove();
}
}
_
Notez que Iterator.remove()
est le seul moyen sûr de modifier une collection pendant l’itération; le comportement n'est pas spécifié si la collection sous-jacente est modifiée d'une autre manière pendant le déroulement de l'itération.
Source: docs.Oracle> L'interface de collection
Et de même, si vous avez un ListIterator
et que vous voulez ajouter éléments, vous pouvez utiliser ListIterator#add
, pour la même raison vous pouvez utiliser _Iterator#remove
_ - il est conçu pour le permettre.
Dans votre cas, vous avez essayé de supprimer d'une liste, mais la même restriction s'applique si vous tentez de put
dans un Map
tout en itérant son contenu.
Cela marche:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next() == 5) {
iter.remove();
}
}
J'ai supposé que comme une boucle foreach est un sucre syntaxique pour les itérations, utiliser un itérateur ne servirait à rien ... mais cela vous donne cette fonctionnalité .remove()
.
Avec Java 8, vous pouvez utiliser la nouvelle méthode removeIf
. Appliqué à votre exemple:
Collection<Integer> coll = new ArrayList<>();
//populate
coll.removeIf(i -> i == 5);
Puisque la question a déjà été répondue, c’est-à-dire que le meilleur moyen est d’utiliser la méthode remove de l’objet itérateur, j’entrerai dans les détails de l’endroit où l’erreur "Java.util.ConcurrentModificationException"
est renvoyée.
Chaque classe de collection a une classe privée qui implémente l'interface Iterator et fournit des méthodes telles que next()
, remove()
et hasNext()
.
Le code pour next ressemble à quelque chose comme ça ...
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Ici la méthode checkForComodification
est implémentée comme
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Ainsi, comme vous pouvez le constater, si vous essayez explicitement de supprimer un élément de la collection. Il en résulte que modCount
devient différent de expectedModCount
, ce qui entraîne l'exception ConcurrentModificationException
.
Vous pouvez soit utiliser l'itérateur directement, comme vous l'avez mentionné, ou bien conserver une deuxième collection et ajouter chaque élément que vous souhaitez supprimer à la nouvelle collection, puis supprimer Tout à la fin. Cela vous permet de continuer à utiliser la sécurité de type de la boucle for-each au prix d'une utilisation accrue de la mémoire et du temps processeur (cela ne devrait pas être un gros problème à moins que vous n'ayez de très grandes listes ou un très vieil ordinateur)
public static void main(String[] args)
{
Collection<Integer> l = new ArrayList<Integer>();
Collection<Integer> itemsToRemove = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l)
{
if (i.intValue() == 5)
itemsToRemove.add(i);
}
l.removeAll(itemsToRemove);
System.out.println(l);
}
Dans de tels cas, une astuce courante consiste (était?) À revenir en arrière:
for(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
Cela dit, je suis plus qu'heureux que vous disposiez de meilleures méthodes dans Java 8, par exemple. removeIf
ou filter
sur les flux.
Même réponse que Claudius avec une boucle for:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
Object object = it.next();
if (test) {
it.remove();
}
}
Avec Eclipse Collections (anciennement GS Collections ), la méthode removeIf
définie sur MutableCollection fonctionnera:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
Avec Java 8 syntaxe Lambda, ceci peut être écrit comme suit:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
L'appel à Predicates.cast()
est nécessaire ici car une méthode removeIf
par défaut a été ajoutée à l'interface Java.util.Collection
dans Java 8.
Remarque: Je suis un committer pour Eclipse Collections .
Faites une copie de la liste existante et parcourez une nouvelle copie.
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
Avec une boucle traditionnelle
ArrayList<String> myArray = new ArrayList<>();
for (int i = 0; i < myArray.size(); ) {
String text = myArray.get(i);
if (someCondition(text))
myArray.remove(i);
else
i++;
}
Les gens affirment qu'un ne peut pas supprimer d'une collection itérée par une boucle foreach. Je voulais juste souligner que est techniquement incorrect et décrire exactement (je sais que la question du PO est suffisamment avancée pour éviter de le savoir) le code derrière cette hypothèse:
for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes
if (obj.isTouched()) {
untouchedSet.remove(obj);
touchedSt.add(obj);
break; // this is key to avoiding returning to the foreach
}
}
Ce n'est pas que vous ne pouvez pas supprimer de la version itérée Colletion
mais que vous ne pouvez pas continuer l'itération une fois que vous le faites. D'où le break
dans le code ci-dessus.
Toutes mes excuses si cette réponse est un cas d’utilisation un peu spécialisé et plus adapté à l’original fil Je suis arrivé ici de celui-ci, celui-ci est marqué comme un doublon (bien que ce fil paraisse plus nuancé) et verrouillé.
Un ListIterator
vous permet d'ajouter ou de supprimer des éléments de la liste. Supposons que vous ayez une liste d'objets Car
:
List<Car> cars = ArrayList<>();
// add cars here...
for (ListIterator<Car> carIterator = cars.listIterator(); carIterator.hasNext(); )
{
if (<some-condition>)
{
carIterator().remove()
}
else if (<some-other-condition>)
{
carIterator().add(aNewCar);
}
}
Une autre méthode consiste à créer une copie de votre tableau arrayList:
List<Object> l = ...
List<Object> iterationList = ImmutableList.copyOf(l);
for (Object i : iterationList) {
if (condition(i)) {
l.remove(i);
}
}
La meilleure façon (recommandée) est d'utiliser le package Java.util.Concurrent. En utilisant ce package, vous pouvez facilement éviter cette exception. se référer au code modifié
public static void main(String[] args) {
Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l) {
if (i.intValue() == 5) {
l.remove(i);
}
}
System.out.println(l);
}
J'ai une suggestion pour le problème ci-dessus. Pas besoin de liste secondaire ni de temps supplémentaire. Veuillez trouver un exemple qui ferait la même chose mais d'une manière différente.
//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
Object r = list.get(index);
if( state ) {
list.remove(index);
index = 0;
continue;
}
index += 1;
}
Cela éviterait l'exception de concurrence.
ConcurrentHashMap ou ConcurrentLinkedQueue ou ConcurrentSkipListMap peut être une autre option, car ils ne liront jamais d'exception ConcurrentModificationException, même si vous supprimez ou ajoutez un élément.
Je sais que cette question suppose juste un Collection
, et pas plus précisément un quelconque List
. Mais pour ceux qui lisent cette question et qui travaillent effectivement avec une référence List
, vous pouvez éviter ConcurrentModificationException
avec une boucle while
- (en la modifiant) si vous voulez pour éviter Iterator
(soit si vous voulez l'éviter en général, ou bien évitez-le spécifiquement pour obtenir un ordre de bouclage différent de l'arrêt du début à la fin de chaque élément [que je crois être le seul ordre Iterator
lui-même peut faire]):
* Mise à jour: Voir les commentaires ci-dessous qui expliquent ce qui est analogue est également réalisable avec le traditionnel - for-loop.
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 1;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i++);
} else {
i += 2;
}
}
Aucune exception ConcurrentModificationException de ce code.
Nous voyons là que la boucle ne commence pas au début, et ne s’arrête pas à tous les éléments (ce que je crois que Iterator
ne peut pas faire).
FWIW, nous voyons aussi que get
est appelée sur list
, ce qui ne pourrait pas être fait si sa référence était simplement Collection
(au lieu du type plus spécifique List
- de Collection
) - List
interface comprend get
, mais pas Collection
interface. Sinon pour cette différence, la référence list
pourrait plutôt être un Collection
[et, techniquement, cette réponse serait alors une réponse directe, au lieu d'une réponse tangentielle].
Le même code FWIW fonctionne toujours après modification pour commencer au début à chaque arrêt (de la même manière que Iterator
ordre):
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 0;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i);
} else {
++i;
}
}
Une solution pourrait consister à faire pivoter la liste et à supprimer le premier élément pour éviter les exceptions ConcurrentModificationException ou IndexOutOfBoundsException.
int n = list.size();
for(int j=0;j<n;j++){
//you can also put a condition before remove
list.remove(0);
Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
Si vous ignorez l'appel interne iterator.next (), vous devez supprimer l'élément de la liste. ça fonctionne encore! Bien que je ne propose pas d'écrire un code comme celui-ci, il est utile de comprendre le concept sous-jacent :-)
À votre santé!
Exemple de modification de collection thread-safe:
public class Example {
private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
public void removeFromQueue() {
synchronized (queue) {
Iterator<String> iterator = queue.iterator();
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
}
}
Je sais que cette question est trop ancienne pour traiter de Java 8, mais pour ceux qui utilisent Java 8, vous pouvez facilement utiliser removeIf ():
Collection<Integer> l = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
l.removeIf(i -> i.intValue() == 5);
Dans le cas où ArrayList: remove (int index) - if (index est la position du dernier élément), il évite sans System.arraycopy()
et ne prend pas de temps pour cela.
le temps de arraycopy augmente si (l'index diminue), en même temps que les éléments de la liste diminuent!
le meilleur moyen efficace de supprimer est de supprimer ses éléments dans l'ordre décroissant: while(list.size()>0)list.remove(list.size()-1);
// prend O(1) while(list.size()>0)list.remove(0);
// prend O (factorial (n))
//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
Integer integer = rdm.nextInt();
ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion
// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++)
if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion
// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--)
if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion
// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion