isPresent()を呼び出さずにOptional.get()が悪いのに、iterator.next()が悪いのはなぜですか?


23

新しいJava8ストリームAPIを使用してコレクション内の特定の要素を検索する場合、次のようなコードを記述します。

    String theFirstString = myCollection.stream()
            .findFirst()
            .get();

ここで、IntelliJは、isPresent()を最初にチェックせずにget()が呼び出されることを警告しています。

ただし、このコード:

    String theFirstString = myCollection.iterator().next();

...警告は表示されません。

ストリーミングアプローチを何らかの方法でより「危険」にし、最初にisPresent()を呼び出さずにget()を呼び出さないことが重要であるという2つの手法の間に大きな違いがありますか?いろいろと調べてみると、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つの要素を選択できるようになりたいと思っていますが、それはこれらの種類の構造に慣れる必要があるということです。


7
あなたが使用している場合、あなたの最後の例のフォームの部分をhrowing例外はたくさんより簡潔に書くことができorElseThrowを -と、あなたが、例外がスローされることを意図していることをすべての読者には明らかだろう。
ハルク

回答:


12

まず、チェックが複雑で、おそらく比較的時間がかかることを考慮してください。静的解析が必要であり、2つの呼び出しの間にかなりのコードが存在する場合があります。

第二に、2つの構造の慣用的な使用法は非常に異なります。私はScalaでフルタイムでプログラミングを行ってOptionsいます。ScalaはJavaよりもはるかに頻繁に使用します。私は、私が使用するために必要な単一インスタンス思い出すことができない.get()上にOption。ほとんど常にOptional関数からを返すか、orElse()代わりに使用する必要があります。これらは、例外をスローするよりも欠損値を処理するはるかに優れた方法です。

ので.get()めったに通常の使用で呼び出されるべきではない、それは見ているとき、IDEは、複雑なチェックを開始するために、それは理にかなって.get()、それが適切に使用されたかどうかを確認します。対照的iterator.next()に、イテレータの適切で普遍的な使用法です。

言い換えると、一方が悪いことではなく、もう一方がそうでないことではありません。一方は、操作の基本であり、開発者のテスト中に例外をスローする可能性が高い一般的なケースの問題です。開発者のテスト中に例外をスローするほど十分に行使されない場合があります。これらは、IDEに警告するような種類のケースです。


このオプションのビジネスについてもっと学ぶ必要があるかもしれません-私は、主にjava8を、webappでよく行うオブジェクトのリストのマッピングを行う良い方法として見ました。あなたはOptional <>をどこにでも送ることになっていますか?これには慣れるのに多少時間がかかりますが、それは別のSEの投稿です!:)
テレビのフランク

@TVのフランクどこにでもあるはずではなく、含まれている型の値を変換する関数を作成できるようになり、mapまたはで連鎖させることができflatMapます。Optionalを使用すると、すべてをnull小切手で囲む必要がなくなります。
モルゲン

15

Optionalクラスは、含まれるアイテムが存在するかどうかがわからない場合に使用することを目的としています。警告は、プログラマーが適切に処理できる可能性のある追加のコードパスがあることをプログラマに通知するために存在します。例外はスローされることをまったく避けることです。あなたが提示するユースケースはそれが設計されたものではないため、警告はあなたとは無関係です-実際に例外をスローしたい場合は、先に進んで無効にしてください(ただし、グローバルではなくこの行のみ-インスタンスごとに存在しないケースをインスタンスごとに処理するかどうかを決定すると、警告がこれを行うように通知します。

おそらく、イテレーターに対して同様の警告が生成されるはずです。ただし、これはまだ一度も行われたことがないため、既存のプロジェクトに警告を追加しすぎると、おそらく良いアイデアとはなりません。

また、独自の例外をスローすることは、デフォルトの例外よりも有用である可能性があることに注意してください。どの要素が存在すると予想されていたのか、後のデバッグではあまり役に立たなかったためです。


6

これらに本質的な違いはないことに同意しますが、より大きな欠陥を指摘したいと思います。

どちらの場合も、動作することが保証されていないメソッドを提供する型があり、その前に別のメソッドを呼び出すことになっています。IDE /コンパイラ/などが1つのケースについて警告するかどうかは、このケースをサポートするか、またはそのように構成しているかによって決まります。

ただし、どちらのコードもコードのにおいです。これらの式は、基礎となる設計上の欠陥を示唆し、脆弱なコードを生成します。

もちろん、早く失敗したいというのは正しいことですが、少なくとも私にとっての解決策は、ただそれを一掃して空中に投げ出すことではありません(またはスタックへの例外)。

あなたのコレクションは今のところ空ではないことが知られているかもしれませんが、あなたが本当に信頼できるのはそのタイプだけです:Collection<T>-そしてそれはあなたが空でないことを保証しません。空ではないことがわかったとしても、要件を変更すると、コードの前払いが変更され、コードが突然空のコレクションにヒットする可能性があります。

これで、空でないリストを取得することになっていたことを他の人に自由にプッシュして伝えることができます。その「エラー」を早く見つけて幸せになることができます。それでも、ソフトウェアの破損部分があり、コードを変更する必要があります。

これをより実用的なアプローチと比較してください。コレクションを取得し、空になる可能性があるため、Optional#map、#orElse、または#get以外のこれらのメソッドを使用して、そのケースに対処する必要があります。

コンパイラは可能な限り多くの作業を行うため、を取得するという静的タイプのコントラクトに可能な限り依存できますCollection<T>。コードがこの契約に違反しない場合(これら2つの臭いコールチェーンのいずれかを介してなど)、コードは環境条件の変化に対してもろくなりません。

上記のシナリオでは、空の入力コレクションを生成する変更は、コードにはまったく影響しません。これは別の有効な入力であるため、正しく処理されます。

このコストは、a)脆弱なコードを危険にさらしたり、b)例外をスローして不必要に複雑にしたりするのではなく、より良い設計に時間を費やす必要があることです。

最後の注意事項:すべてのIDE /コンパイラが、事前のチェックなしでiterator()。next()またはoptional.get()呼び出しについて警告するわけではないため、このようなコードは通常レビューでフラグが立てられます。それが価値があるものについては、そのようなコードがレビューを通過させ、代わりにクリーンなソリューションを要求することを許可しません。


コードの匂いについては少し同意できると思います-短い投稿をするために上記の例を作成しました。別のより有効なケースは、リストから必須要素を抜き取ることです。これは、アプリケーションIMOに表示されるのは奇妙なことではありません。
テレビのフランク

スポットオン。「リストからプロパティpを持つ要素を選択する」-> list.stream()。filter(p).findFirst()..さらに、「そこにない場合はどうなるのか」という質問をするように強制されます。 ..毎日のパンとバター。それが必須で「ちょうどそこに」ある場合-そもそもなぜ他の多くのものにそれを隠したのですか?
フランク

4
おそらくそれはデータベースからロードされるリストだからです。リストは他の関数で収集された統計コレクションかもしれません:オブジェクトA、B、Cがあることがわかっているので、Bのすべての量を検索したいです。
テレビのフランク

私はあなたのデータベースについて知りませんが、私のクエリごとに少なくとも1つの結果を保証するものではありません。統計収集についても同様です-常に空ではないと言うのは誰ですか?私は、これまたはその要素があるべきであると推論するのは簡単であることに同意しますが、ドキュメントよりも適切な静的型付けを好むか、または議論からのいくらかの洞察を悪化させます。
フランク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.