目次
はじめに
ピットを踏む操作
foreach ループの削除方法
添字ループ操作
正しい方法
イテレータメソッド
Lambda表达式
Stream流操作
中间对象辅助方式
ホームページ Java &#&チュートリアル JAVAの単純なforループで例外を回避するにはどうすればよいですか?

JAVAの単純なforループで例外を回避するにはどうすればよいですか?

Apr 26, 2023 pm 12:58 PM
java for

はじめに

実際のビジネスプロジェクト開発では、与えられたリストから条件を満たさない要素を削除するという操作は誰でも経験があるのではないでしょうか?

多くの学生はそれを達成するためのさまざまな方法をすぐに思いつきますが、あなたが考えるこれらの実装方法は人体や動物に無害ですか?一見普通に見える操作の多くは実は罠であり、多くの初心者は注意しないと罠に陥る可能性があります。

誤って踏んだ場合:

  • コードは実行時に直接例外をスローし、エラーを報告します。これは不幸中の幸いです。少なくとも、それは可能です。時間内に発見され解決される

  • コードはエラーなく実行されますが、さまざまな奇妙な問題がビジネス ロジックに不可解に現れます。この問題に注意を払わないと、これはさらに悲劇的です。その後のビジネスに隠れた危険を引き起こす可能性があります。

それでは、どのような実装方法があるのでしょうか?どの実装方法に問題がある可能性がありますか?ここで一緒に話し合いましょう。ここで議論されているのは、フェンネル豆で「フェンネル」という単語をどのように書くかという問題ではなく、非常に深刻で実際的であり、無視されがちな技術的な問題であることに注意してください。

仮想の需要シナリオ:

ユーザー リスト allUsers がある場合、下位部門が dev である担当者をリストから削除し、残りの担当者情報を返す必要があります

ピットを踏む操作

foreach ループの削除方法

多くの初心者が最初に考えるのは、for ループを 1 つずつ確認して、条件を満たすものを削除することです。とても簡単です...

1 分でコードを書き終えました:

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

それから自信を持って実行ボタンをクリックしました:

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)
ログイン後にコピー

え?何してるの?なぜ例外がスローされるのでしょうか?

注意しないと罠にはまってしまうので、なぜ例外がスローされるのかを分析してみましょう。

原因分析:

JAVA の foreach 構文の実際の処理は、反復子 Iterator に基づいています。

ループの開始時に、最初に反復インスタンスが作成され、この反復インスタンスの ExpectedModCount にコレクションの modCount が割り当てられます。イテレータが hashNext() / next() を使用して次の要素をトラバースするたびに、modCount 変数と ExpectModCount 値が等しいかどうかをチェックします。等しい場合はトラバースが返され、そうでない場合は ConcurrentModificationException がスローされます。トラバースを終了します。

ループ内で要素を追加または削除する場合は、コレクションの add() メソッドと Remove() メソッドを直接呼び出して、modCount を増減させますが、これらのメソッドは、反復インスタンスの結果: 反復インスタンスの ExpectedModCount と modCount の値が等しくない場合、ConcurrentModificationException 例外がスローされます。

JAVAの単純なforループで例外を回避するにはどうすればよいですか?

添字ループ操作

え? foreach メソッドが機能しないので、元の添字ループ メソッドを使用して実行すると、エラーは発生しませんよね。まだとても簡単です...

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;
}
ログイン後にコピー

コードは一度で完成します。実行して、処理された出力を確認してください:

{id=2, name='李思' 、部門='開発'}
{id=3、名前='王五'、部門='製品'}
{id=4、名前='鉄竹'、部門='午後'}

案の定、エラーは報告されず、結果も出力されました。完璧です~

お待ちください?本当にこれでいいのでしょうか?

コードのロジックは、"dev".equals(Department) かどうかを判断することですが、出力結果に、削除する必要がある、Department=dev のようなデータがまだ残っているのはなぜですか?

これが実際のビジネス プロジェクトの場合、開発段階でエラーが報告されず、その結果が慎重に検証されてから生産ラインに流れないと、ビジネス ロジックに異常が発生する可能性があります。

この現象の具体的な理由を見てみましょう。

原因分析:

リスト内の要素と添字の間には、実際には強い結合関係はなく、単に位置の順序が対応しているだけであることがわかっています。リストが変更されると、各要素に対応する添字が以下のように変更される場合があります。

JAVAの単純なforループで例外を回避するにはどうすればよいですか?

次に、リストから要素を削除した後、削除された要素以降のすべての要素がリストから変更されます。リストの添字は前方に移動されますが、for ループのポインタ i は常に後方に累積されます。次の要素を処理するときに、一部の要素が欠落して処理されない可能性があります。

例えば下図のように、i=0の場合はA要素の削除が必要と判断して直接削除しますが、再循環する場合はi=1となり、この時、リスト内の要素の位置が前方に移動するため、B 要素は次のようになります。添字 0 の元の位置が直接欠落しました:

JAVAの単純なforループで例外を回避するにはどうすればよいですか?

これで、次のようになります。上記のコードが実行後に見逃される理由を知ってください。La~

正しい方法

上記の 2 つの落とし穴を見た後、正しく適切な操作方法は何でしょうか?

イテレータメソッド

え?それは正しい? foreach メソッドでもイテレータが使用されると言いましたが、これは実際にはトラップ操作ですか?ここでイテレータ パターンが正しい方法であると言われているのはなぜですか?

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

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,所以在迭代器中增加、删除元素是可以正常运行的。,所以这样就不会出问题啦。

JAVAの単純なforループで例外を回避するにはどうすればよいですか?

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;
}
ログイン後にコピー

以上がJAVAの単純なforループで例外を回避するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプ Java での日付までのタイムスタンプ Aug 30, 2024 pm 04:28 PM

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルの量を見つけるためのJavaプログラム カプセルの量を見つけるためのJavaプログラム Feb 07, 2025 am 11:37 AM

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

See all articles