新しいJava8ストリームAPIを使用してコレクション内の特定の要素を1つ検索する場合、次のようなコードを記述します。
String theFirstString = myCollection.stream()
.findFirst()
.get();
ここで、IntelliJは、isPresent()を最初にチェックせずにget()が呼び出されることを警告しています。
ただし、このコード:
String theFirstString = myCollection.iterator().next();
...警告は出されません。
2つの手法の間にいくつかの大きな違いはありますか?それはストリーミングアプローチを何らかの方法で「危険」にします。最初にisPresent()を呼び出さずにget()を呼び出さないことが重要ですか?探し回って、プログラマーがOptional <>に不注意であることについて話している記事を見つけ、いつでもget()を呼び出すことができると想定しています。
ことは、コードがバグがある場所のできるだけ近くで例外がスローされるようにすることです。この場合、要素が見つからないときにget()を呼び出します。次のような無駄なエッセイを書く必要はありません。
Optional<String> firstStream = myCollection.stream()
.findFirst();
if (!firstStream.isPresent()) {
throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
}
String theFirstString = firstStream.get();
... get()からの例外を発生させるのに私が知らない危険がある場合を除きますか?
Karl Bielefeldtからの応答とHulkからのコメントを読んだ後、上記の例外コードが少し不格好であることに気付きました。
String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
String firstString = myCollection.stream()
.findFirst()
.orElseThrow(() -> new IllegalStateException(errorString));
これはより便利に見え、おそらく多くの場所で自然になるでしょう。これをすべて処理しなくても、リストから1つの要素を選択できるようにしたいのですが、それだけで、これらの種類の構成に慣れる必要があります。
まず第一に、チェックは複雑で、おそらく比較的時間がかかることを考慮してください。いくつかの静的分析が必要であり、2つの呼び出しの間にかなりのコードが存在する可能性があります。
第2に、2つの構成の慣用的な使用法は非常に異なります。私はScalaでフルタイムでプログラミングしています。これは、Options
をJavaよりも頻繁に使用します。Option
で.get()
を使用する必要があった単一のインスタンスを思い出せません。ほとんどの場合、関数からOptional
を返すか、代わりにorElse()
を使用する必要があります。これらは、例外をスローするよりも欠損値を処理する優れた方法です。
.get()
は通常の使用ではめったに呼び出されないため、.get()
が検出されたときにIDEが複雑なチェックを開始して、それが適切に使用されたかどうかを確認することは理にかなっています対照的に、iterator.next()
は、イテレータの適切でユビキタスな使用法です。
言い換えると、どちらかが悪いことではなく、もう一方が問題ではないことです。1つは操作の基本であり、開発者のテスト中に例外をスローする可能性が高い一般的なケースであり、もう1つはめったに使用されないことです開発者のテスト中に例外をスローするほど十分に行使されない場合があります。これらは、IDEが警告するようなケースです。
Optionalクラスは、それに含まれるアイテムが存在するかどうかわからない場合に使用することを目的としています。警告は、正常に処理できる可能性のある追加の可能なコードパスがあることをプログラマに通知するために存在します。この考え方は、例外がスローされないようにすることです。あなたが提示するユースケースはそれが設計されたものではないため、警告はあなたには関係ありません-実際に例外をスローしたい場合は、先に進んでそれを無効にしてください(この行に対してのみで、グローバルではありません-あなたは存在しないケースをインスタンスごとに処理するかどうかを決定すると、警告が表示されます。
おそらく、同様の警告がイテレータに対して生成されるべきです。ただし、これは決して行われたことがなく、今追加すると、おそらく既存のプロジェクトで警告が多すぎて良いアイデアにならないでしょう。
また、独自の例外をスローする方が、デフォルトの例外だけでなく、存在することが期待されていた要素の説明が含まれているため、後でデバッグする際にはあまり役立ちません。
これらに本質的な違いはないことに同意しますが、もっと大きな欠陥を指摘したいと思います。
どちらの場合も、動作することが保証されていないメソッドを提供するタイプがあり、その前に別のメソッドを呼び出す必要があります。 IDE /コンパイラ/などがどちらかのケースについて警告するかどうかは、それがこのケースをサポートしているかどうか、および/またはそうするように構成しているかどうかに依存します。
とはいえ、どちらのコードもコードのにおいです。これらの表現は、根本的な設計上の欠陥を示唆し、脆弱なコードを生成します。
もちろん、あなたは速く失敗したいと思っていますが、少なくとも私にとっての解決策は、それを肩からすくめて、手を空中に投げる(または例外をスタックに置く)ことではありません。
現在、コレクションが空ではないことがわかっている可能性がありますが、実際に信頼できるのはそのタイプCollection<T>
だけです。これは空でないことを保証するものではありません。空ではないことがわかったとしても、要件を変更すると、コードが前もって変更され、突然、コードが空のコレクションにヒットする可能性があります。
これで、あなたは自由にプッシュバックして、空でないリストを取得することになっていたことを他の人に伝えることができます。あなたはその「エラー」を早く見つけて幸せになることができます。それでも、壊れたソフトウェアがあり、コードを変更する必要があります。
これをより実用的なアプローチと比較してください。コレクションを取得すると空になる可能性があるため、Optional#map、#orElse、または#get以外のこれらのメソッドを使用して、そのような場合に対処する必要があります。
コンパイラーは、Collection<T>
を取得する静的型コントラクトに可能な限り依存できるように、可能な限り多くの作業を行います。コードがこのコントラクトに違反しない場合(これらの2つの悪臭のある呼び出しチェーンのいずれかを介するなど)、コードは環境条件の変化に対してはるかに脆弱ではありません。
上記のシナリオでは、空の入力コレクションを生成する変更は、コードに何の影響も及ぼしません。これはもう1つの有効な入力であるため、正しく処理されます。
これのコストは、a)脆弱なコードのリスクやb)例外を投げて不必要に複雑にするのではなく、より良い設計に時間を費やす必要があることです。
最後に、すべてのIDE /コンパイラが事前のチェックなしにiterator()。next()またはoptional.get()の呼び出しについて警告するわけではないため、このようなコードは通常レビューでフラグが付けられます。それだけの価値があるので、私はそのようなコードがレビューを通過することを許可せず、代わりにクリーンなソリューションを要求します。