原理分析からストリームを理解する

概要: Stream は jdk1.8 から提供された新機能です。

この記事は、Huawei クラウド コミュニティ「ストリームの深い理解の原則の分析」、著者: Li Ge Technology から共有されています。

Stream は jdk1.8 によって提供された新しい機能です. これにより主に宣言的な方法でデータコレクションを処理できます. コレクションがプロジェクトで最も一般的に使用されるデータストレージ構造であることを知る必要があります. フィルタリングやその他の処理を行うには要素の操作、通常は for ループを使用します。

ストリーム操作の分類

Stream の操作は、中間操作と最終操作の 2 つのカテゴリに分けることができます。

中間操作は操作を記録するだけで、最後の操作だけが実際の計算をトリガーします。これは遅延読み込みと理解できます。これは、大きなオブジェクトを繰り返し計算するときに Stream が非常に効率的である理由の 1 つです。

中間操作は、ステートフル操作とステートレス操作に分けられます. ステートレスとは、要素の処理が前の要素の影響を受けないことを意味し、ステートフルとは、すべての要素を取得した後にのみ操作を続行できることを意味します. ステートフルなdistinct()重複排除メソッドなど、これもわかりやすいですが、彼は他の値を気にすることはできないと思いますか? もちろんそうではありません。現在の反復要素が繰り返されているかどうかを知るために、すべての要素を取得する必要があります。

最終操作は、短絡操作と非短絡操作に分けることができます.これはよく理解する必要があります.短絡とは、条件を満たす特定の要素に遭遇することによって最終結果が得られることを意味します.非短絡とは、短絡を意味します.最終結果を得るには、すべての要素を処理する必要があります。

このように細分化されている理由は、下層のレイヤーがそれぞれの状況を異なる方法で処理するためです。

ストリーム構造分析

次のコードを簡単に見てみましょう。

 List<String> list = new ArrayList<>();
 // 获取stream1
 Stream<String> stream1 = list.stream();
 // stream1通过filter后得到stream2
 Stream<String> stream2 = stream1.filter("lige"::equals);
 // stream1与stream2是同一个对象吗?
 System.out.println("stream1.equals(stream2) = " + stream1.equals(stream2));
 System.out.println("stream1.classTypeName = " + stream1.getClass().getTypeName());
 System.out.println("stream2.classTypeName = " + stream2.getClass().getTypeName());
 // 结果
 // stream1.equals(stream2) = false
 // stream1.classTypeName = java.util.stream.ReferencePipeline$Head
 // stream1.classTypeName = java.util.stream.ReferencePipeline$2

明らかに、stream1 と stream2 は同じオブジェクトではなく、同じ実装クラスでもありません。stream1 の実装クラスは ReferencePipeline$Head であり、stream2 の実装クラスは匿名の内部クラスです. ソース コードを分析してみましょう. いわゆるソース コードの下に隠れるものは何もありません.

stream2 をもう一度見てみましょう。

分析すると、stream2 の実装クラスは StatelessOp であることがわかり、このような構造が形成されます。

各中間操作は、新しい Stream を生成します。ステートレス操作の場合、実装クラスは StatelessOp であり、ステートフル操作の場合、実装クラスは StatefulOp です。

それらの間の継承関係をもう一度見てみましょう。

コアシンクについてもう一度話しましょう

実際、Stream API の内部実装の本質は、Sink の 4 つのインターフェイス メソッドをオーバーロードする方法です。

私はまだ例から始めます:

List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("ligeligeligeligeligeligeligeligeligelige");
list.add("lisilisilisilisilisilisilisilisi");
list.add("wangwu");
list.add("ligejishuligejishuligejishuligejishuligejishuligejishuligejishu");
List<String> resultList = list.stream()
 .filter(it -> it.contains("li"))// 1. 只要包含li的数据
 .filter(it -> it.contains("lige"))// 2. 只要包含lige的数据
 .map(String::toUpperCase)// 3. 对符合的数据作进一步加工,转换大写
 .map(String::toLowerCase)// 4. 对符合的数据作进一步加工,转换小写
 .collect(Collectors.toList());
resultList.forEach(System.out::println);

filter メソッド、map メソッド、またはその他のメソッドのいずれであっても、ソース コード レベルに入り、StatelessOp オブジェクトまたは StatefulOp オブジェクトを返します。

したがって、次のような構造があります。

しかし、それはシンクと何の関係があるのでしょうか? フィルターまたはマップのソース コードを順番に見てみましょう。

匿名の StatelessOp オブジェクトを直接返し、opWrapSink メソッドを実装します。opWrapSink メソッドは、シンク オブジェクトを渡し、別のシンク オブジェクトを返します。また、新しいシンク オブジェクトには、着信シンク オブジェクトへの参照があります。

しかし、このコードの用途は何ですか? いつ発動した?

心配しないで、collect(Collectors.toList()) メソッドから始めて、段階的に詳しく見ていきましょう。

ここで、xx メソッドに渡されるターミナル オブジェクトが ReduceOp であることを知る必要があります。この ReduceOp オブジェクトは、makeSink のときに匿名の内部クラスの ReducingSink オブジェクトを返します。

ここで makeSink について言及し、匿名の内部クラスである ReducingSink オブジェクトを返します。

まず warpSink を実行し、次に copyInto を実行します。率直に言うと、まずシンクをチェーン シンクにパッケージ化し、次にシンク チェーンをトラバースして結果オブジェクトにコピーします。ここでの両方のステップは非常に重要です。

最初に warpSink を見てください。

  1. 初めて入るときは、これが最後の Stream オブジェクトで、末尾から先頭に移動します。
  2. トラバースされるたびに、新しい Stream オブジェクト (通常は StatelessOp オブジェクトまたは StatefulOp オブジェクト) が取得されます。
  3. 匿名実装である操作オブジェクトの opWrapSink メソッドを実行します。
  4. 各 opWrapSink 実装メソッドでは、前のシンクが渡され、最後にシンク リストが取得されます。

最後に、内部的にパッケージ化されたシンクと呼ばれるシンク チェーンのヘッド ノードに戻り、その名前をラップしてから、begin、forEachRemaining、および end メソッドを実行する準備をします。

forEachRemaning は最後に accept メソッドを呼び出します。

Streamの実行過程がわかるアニメーション

 

フォローをクリックして、HUAWEI CLOUDの新技術について初めて学びましょう〜

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4526289/blog/5572070