web-dev-qa-db-ja.com

Javaでは、ループに対するストリームの利点は何ですか?

私は面接でこれを頼まれました、そして私が私が持つことができる最も良い答えをしたと確信していません。私はあなたが並列検索を行うことができると私は覚えていないことができるいくつかの手段によってnull値が処理されたことを述べた。今、私はオプショナルを考えていたことに気づきます。私はここで何が足りないのですか?彼らは、それがより簡潔でより簡潔なコードだと主張していますが、私は同意します。


それがどれほど簡潔に答えられたかを考えると、結局のところこれはあまりにも広範な質問ではなかったようです。


彼らがインタビューでこの質問をしている、そして明らかにそうであるならば、答えを見つけることを難しくすること以外にそれを打破することにどんな目的が役立つでしょうか?つまり、あなたは何を探していますか?私は質問を細分化してすべてのサブ質問に回答させることができましたが、それからすべてのサブ質問へのリンクを持つ親の質問を作成します。私達がそれにたどり着いている間、私にそれほど広くない質問の例を挙げてください。私はこの質問の一部だけを尋ねても意味のある答えを得ることはできません。まったく同じ質問を別の方法ですることもできます。たとえば、「ストリームにはどのような目的がありますか」と尋ねることができます。または "forループの代わりにストリームを使用するのはいつですか?"または「forループではなくストリームを使用する理由」これらはすべてまったく同じ質問です。

...あるいは、誰かが非常に長いマルチポイントの答えを出したので、広すぎると思いますか?率直に言って、知っている人ならだれでも事実上あらゆる質問でそれをすることができます。たとえば、たまたまJVMの作者の一人であれば、私たちのほとんどができなかったときには、おそらく一日中forループについて話すことができるでしょう。

「質問を編集して、適切な回答を特定するのに十分な詳細さで特定の問題に限定してください。一度に複数の個別の質問をすることは避けてください。この質問を明確にするための質問ページをご覧ください。

以下に記すように、適切な答えが与えられていて、それがあること、そして提供するのが十分に簡単であることを証明しています。

97
user447607

不利な点について尋ねずに、面接の質問が利点について尋ねることは興味深いことです、両方ともあります。

ストリームはより宣言的なスタイルです。あるいはもっと表現力豊かスタイル。それがどのように行われているかを記述するより、コードであなたの意図を宣言するほうが良いと考えられるかもしれません:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

これは、リストから一致する要素を絞り込むことを明確に示しています。

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

「私はループをしています」と言います。ループの目的はロジックの奥深くにあります。

ストリームはしばしば簡潔です。同じ例がこれを示しています。 Terserは常に優れているわけではありませんが、同時に簡潔で表現力に富んでいれば、それだけ優れています。

ストリームは関数と強い親和性があります。 Java 8では、ラムダと機能的なインタフェースが導入されています。これにより、強力なテクニックが揃います。ストリームは、オブジェクトのシーケンスに関数を適用するための最も便利で自然な方法を提供します。

小川はより少ない可変性を奨励する。これは関数型プログラミングの側面に関連したものです。ストリームを使って書くプログラムの種類は、オブジェクトを変更しない種類のプログラムになる傾向があります。

ストリームはより疎結合を奨励します。あなたのストリーム処理コードは、ストリームのソース、またはその最終的な終了方法を知る必要はありません。

ストリームは非常に洗練された振る舞いを簡潔に表現できます。例えば:

 stream.filter(myfilter).findFirst();

一見したところでは、まるでストリーム全体をフィルタリングしてから最初の要素を返すように見えます。しかし実際にはfindFirst()は操作全体を駆動するので、1つの項目を見つけた後は効率的に停止します。

Streamsは将来の効率向上のための範囲を提供します。ベンチマークを行い、インメモリのListsまたは配列からのシングルスレッドストリームは同等のループより遅くなる可能性があることを確認した人もいます。より多くのオブジェクトとオーバーヘッドが場にあるので、これはもっともらしいです。

しかし、ストリームは拡大します。 Javaの並列ストリーム操作の組み込みサポートに加えて、StreamsをAPIとして使用した分散マップリデュース用のライブラリがいくつかあります。これはモデルが適しているためです。

デメリット?

パフォーマンス:配列を通るforループは、ヒープとCPU使用率の両面で非常に軽量です。生の速度とメモリの節約が優先される場合は、ストリームを使用することがより悪くなります。

おなじみの。世界には、ループがよく知られていてストリームが目新しい、多くの言語の背景を持つ経験豊富な手続き型プログラマーがいっぱいです。環境によっては、その種の人になじみのあるコードを書きたいと思うでしょう。

認知的オーバーヘッド。その宣言的な性質、およびその下で起こっていることからの抽象化の増加のために、コードが実行にどのように関連するかについての新しい精神モデルを構築する必要があるかもしれません。実際には、問題が発生した場合、またはパフォーマンスや微妙なバグを詳細に分析する必要がある場合にのみ、これを実行する必要があります。それが「ただうまくいく」とき、それはただうまくいく。

Debuggersは改善されていますが、今でもデバッガでストリームコードをステップスルーするときは、単純なループが変数やコードの場所に非常に近いため、同等のループよりも作業が難しくなります。伝統的なデバッガが動作すること。

188
slim

構文上の問題はさておき、Streamsは無限に大きなデータセットを扱うように設計されていますが、配列、コレクション、そしてIterableを実装するほぼすべてのJava SEクラスは完全にメモリ内にあります。

Streamの短所は、フィルタ、マッピングなどがチェック済み例外をスローできないことです。このため、Streamは中間I/O操作などには適していません。

9
VGR
  1. あなたは間違って理解しました:並列操作はStreamsではなくOptionalsを使います。

  2. ストリームを扱うメソッドを定義することができます。それらをパラメータとして受け取る、それらを返すなどです。ループをパラメータとして取るメソッドを定義することはできません。これにより、一度複雑なストリーム操作を何度も使用することができます。 Javaには欠点があることに注意してください。あなたのメソッドはstream自身のsomeMethod(stream)とは対照的にstream.someMethod()として呼び出す必要があります。

    myMethod2(myMethod(stream.transform(...)).filter(...))
    

    他の多くの言語(C#、Kotlin、Scalaなど)では、何らかの形式の「拡張メソッド」を使用できます。

  3. あなたが逐次操作のみを必要とし、それらを再利用したくない場合でも、ストリームやループを使用することができますが、ストリームに対する単純な操作はループ内の非常に複雑な変更に対応します。

8
Alexey Romanov

シーケンスの要素に関数を適用したいので、シーケンス(配列、コレクション、入力など)をループします。

ストリームを使用すると、シーケンス要素に対して作成関数を作成し、独立した実装最も一般的な関数(マッピング、フィルタリング、検索、並べ替え、収集など)を実行できます。具体的なケース。

したがって、ほとんどの場合、何らかのループ作業があるため、Streamsを使用して少ないコードで表現できます。つまり、読みやすさとなります。

4
wero

並列化と言うのはとても簡単です。 forループと並行して、何百万ものエントリを繰り返してみてください。私たちは多くのCPUに行きますが、速くはありません。そのため、並列に実行するのが簡単であるほどよく、そしてStreamsを使うとこれは簡単です。

私が大好きなのは、冗長です。 どのように彼らがそれをするのではなく、彼らが実際に何をして作り出すのか理解するのに少し時間がかかります。

2
Eugene