Maison  >  Article  >  Java  >  Comment éviter les exceptions dans les boucles for simples en JAVA ?

Comment éviter les exceptions dans les boucles for simples en JAVA ?

WBOY
WBOYavant
2023-04-26 12:58:071096parcourir

Introduction

Dans le développement réel d'un projet d'entreprise, tout le monde devrait être familier avec l'opération de suppression des éléments qui ne remplissent pas les conditions d'une liste donnée, n'est-ce pas ?

De nombreux étudiants peuvent immédiatement penser à de nombreuses façons d'y parvenir, mais ces méthodes de mise en œuvre auxquelles vous pensez sont-elles inoffensives pour les humains et les animaux ? De nombreuses opérations apparemment normales sont en réalité des pièges, dans lesquels de nombreux novices peuvent tomber s’ils ne font pas attention.

Si malheureusement vous marchez dessus :

  • Le code lancera directement une exception et signalera une erreur lors de l'exécution. C'est une bénédiction dans le malheur. . Au moins, vous pouvez le trouver à temps et le résoudre.

  • Le code s'exécute sans erreur, mais la logique métier présente inexplicablement divers problèmes étranges. , car si vous ne faites pas attention à ce problème, cela peut créer des dangers cachés pour les affaires ultérieures.

Alors, quelles sont les modalités de mise en œuvre ? Quelles méthodes de mise en œuvre peuvent poser problème ? Discutons-en ensemble ici. Veuillez noter que ce qui est discuté ici n'est pas la question de savoir comment écrire le mot « fenouil » dans les haricots de fenouil, mais un problème technique très sérieux et pratique qui est facilement ignoré.

Scénario de demande hypothétique :

Étant donné une liste d'utilisateurs allUsers, il est nécessaire de supprimer de la liste le personnel dont l'affiliation est dev, et de renvoyer les informations personnelles restantes #🎜🎜 #

Marcher sur les opérations de fosse

méthode d'élimination des boucles foreach

La première pensée de nombreux novices est de vérifier les boucles for une par une, puis d'éliminer celles qui se rencontrent les conditions. ~ si facile...

J'ai fini d'écrire le code en 1 minute :

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

Puis j'ai cliqué sur le bouton exécuter en toute confiance :

java.util.ConcurrentModificationException: null
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13)
	at com.veezean.demo4.Main.main(Main.java:26)
# 🎜🎜# Hein ? que fais-tu ? Pourquoi l'exception est-elle levée ?

Je suis entré dans une fosse sans y prêter attention. Analysons pourquoi une exception est levée.

Analyse des causes :

Le traitement réel de la syntaxe foreach de JAVA est basé sur l'itérateur Iterator.

Au début de la boucle, une instance d'itération sera d'abord créée, et le ModCount attendu de cette instance d'itération se verra attribuer le modCount de la collection. Chaque fois que l'itérateur utilise hashNext() / next() pour parcourir l'élément suivant, il vérifiera si la variable modCount et la valeur attendueModCount sont égales. Si elles sont égales, le parcours sera renvoyé, sinon une ConcurrentModificationException sera levée. terminer le parcours.

Si vous ajoutez ou supprimez des éléments dans une boucle, vous appelez directement les méthodes add() et remove() de la collection, provoquant une augmentation ou une diminution du modCount, mais ces méthodes ne modifieront pas le ModCount attendu dans l'instance d'itération. Par conséquent, les valeurs de ExpectedModCount et modCount dans l'instance d'itération ne sont pas égales et une exception ConcurrentModificationException est levée.

Comment éviter les exceptions dans les boucles for simples en JAVA ?Opération de boucle d'indice

Hmm ? Puisque la méthode foreach ne fonctionne pas, utilisez la méthode de boucle d'indice d'origine pour le faire. Vous n'obtiendrez pas d'erreur, n'est-ce pas ? C'est toujours très simple...

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (int i = 0; i < allUsers.size(); i++) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(allUsers.get(i).getDepartment())) {
            allUsers.remove(i);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

Le code est terminé en une seule fois Exécutez-le et voyez le résultat traité :

{id=2, name. ='李思', département='dev'}
{id=3, nom='王五', département='produit'}

{id=4, nom='Tiezhu', département ='pm '}

Effectivement, aucune erreur n'a été signalée et le résultat a été affiché, parfait~

Attendez ? Est-ce vraiment bien ?

La logique de notre code est de juger si "dev".equals(department), mais dans le résultat de sortie, pourquoi y a-t-il encore des données comme département=dev qui devraient être éliminées ?

Si dans un projet d'entreprise réel, aucune erreur n'est signalée pendant la phase de développement et que les résultats ne sont pas soigneusement vérifiés, puis acheminés vers la chaîne de production, cela peut entraîner une logique commerciale anormale.

Revenons sur les raisons précises de ce phénomène.

Analyse des causes :

Nous savons qu'il n'y a en fait pas de relation de liaison forte entre les éléments de la liste et les indices, c'est juste une relation correspondante d'ordre de position, de liste Une fois l'élément du milieu modifié, l'indice correspondant à chaque élément peut changer, comme indiqué ci-dessous :

Comment éviter les exceptions dans les boucles for simples en JAVA ? Ensuite, après avoir supprimé l'élément de la Liste, Les indices de tous les éléments après l'élément supprimé dans la liste sont avancés, mais le pointeur i de la boucle for est toujours accumulé vers l'arrière lors du traitement du suivant, certains éléments peuvent être manqués et ne pas être traités.

Par exemple, comme le montre la figure ci-dessous, lorsque i=0, il est jugé que l'élément A doit être supprimé, et il est supprimé directement lors du recyclage, i=1, à ce stade ; temps, parce que la position de l'élément dans la liste avance, l'élément B devient Il est devenu la position où l'indice d'origine était 0, ce qui a été directement manqué :

Comment éviter les exceptions dans les boucles for simples en JAVA ?Donc ici vous pouvez savoir pourquoi le code ci-dessus Après l'exécution, les poissons passeront à travers le filet~

Voie correcte

Après avoir vu les deux pièges ci-dessus, que devrait être le mode de fonctionnement correct et approprié ?

Méthode itérateur

Eh ? C'est exact? Ne viens-je pas de dire que la méthode foreach utilise également des itérateurs, mais est-ce en fait une opération de piège ? Pourquoi est-il dit ici que le modèle d'itérateur est le bon chemin ?

虽然都是基于迭代器,但是使用逻辑是不一样的,看下代码:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    Iterator<UserDetail> iterator = allUsers.iterator();
    while (iterator.hasNext()) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(iterator.next().getDepartment())) {
            // 这是重点,此处操作的是Iterator,而不是list
            iterator.remove();
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

执行结果:

{id=3, name='王五', department='product'}
{id=4, name='铁柱', department='pm'}

这次竟然直接执行成功了,且结果也是正确的。为啥呢?

在前面foreach方式的时候,我们提过之所以会报错的原因,是由于直接修改了原始list数据而没有同步让Iterator感知到,所以导致Iterator操作前校验失败抛异常了。

而此处的写法中,直接调用迭代器中的remove()方法,此操作会在调用集合的remove(),add()方法后,将expectedModCount重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。,所以这样就不会出问题啦。

Comment éviter les exceptions dans les boucles for simples en JAVA ?

Lambda表达式

言简意赅,直接上代码:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    allUsers.removeIf(user -> "dev".equals(user.getDepartment()));
    return allUsers;
}

Stream流操作

作为JAVA8开始加入的Stream,使得这种场景实现起来更加的优雅与易懂:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    return allUsers.stream()
            .filter(user -> !"dev".equals(user.getDepartment()))
            .collect(Collectors.toList());
}

中间对象辅助方式

既然前面说了不能直接循环的时候执行移除操作,那就先搞个list对象将需要移除的元素暂存起来,最后一起剔除就行啦 ~

嗯,虽然有点挫,但是不得不承认,实际情况中,很多人都在用这个方法 —— 说的就是你,你是不是也曾这么写过?

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> needRemoveUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if ("dev".equals(user.getDepartment())) {
            needRemoveUsers.add(user);
        }
    }
    allUsers.removeAll(needRemoveUsers);
    return allUsers;
}

或者:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> resultUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if (!"dev".equals(user.getDepartment())) {
            resultUsers.add(user);
        }
    }
    return resultUsers;
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer