Java 8の新しい特性のlambdaの役割_ダイナミックノードJavaアカデミー整理

5700 ワード

長い間lambdaがjavaに閉パッケージをもたらす概念を期待していたが,集合でそれを使用しなければ大きな価値を失う.既存のインタフェースの移行がlambdaスタイルになる問題はdefault methodsによって解決されたが,この文章ではJava集合内のバッチデータ操作(bulk operation)を深く解析し,lambdaの最強の役割を果たす神秘的なベールを解く.
1.JSR 335について
JSRはJava Specification Requestsの略で、Java仕様要求を意味し、Java 8バージョンの主な改良はLambdaプロジェクト(JSR 335)であり、Javaがマルチコアプロセッサのためにコードを書きやすくすることを目的としている.
2.外部VS内部反復
以前はJava集合は内部反復を表現することができず,外部反復,すなわちforまたはwhileループのみを提供していた.

List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John"));
for (Person p : persons) {
 p.setLastName("Doe");
}

上記の例は,我々の従来の手法,すなわちいわゆる外部反復であり,ループは固定的な順序ループである.現在のマルチコアの時代には,並列ループをしようとすると,以上のコードを修正せざるを得ない.効率がどれだけ向上するかはもちろん、一定のリスク(スレッドセキュリティの問題など)をもたらします.
内部反復を記述するには、Lambdaのようなクラスライブラリを使用する必要があります.次に、lambdaとCollectionを使用します.forEach上のループを書き換える

persons.forEach(p->p.setLastName("Doe"));

jdkライブラリによってループを制御するようになりました.last nameがどのように各personオブジェクトに設定されているかに関心を持つ必要はありません.ライブラリは、実行環境に応じて、並列、乱順、または怠惰なロード方法を決定することができます.これが内部反復であり,クライアントは動作p.setLastNameをデータとしてapiに転送する.内部反復は実際には集合の一括操作と密接に関連しておらず,文法表現の変化を感じている.本当に興味深いのは、バッチ操作に関連する新しいストリームAPIです.新しいjava.util.streamパッケージがJDK 8に追加されました.
3.Stream API
ストリーム(Stream)はデータストリームのみを表し、データ構造がないため、一度遍歴した後も遍歴できない(これはプログラミング時に注意が必要で、Collectionのように何度遍歴してもデータがある)、そのソースはCollection、array、ioなどである.
3.1中間と終点の方法
ストリームの役割は、大きなデータインタフェースを操作し、データの操作を容易かつ迅速にすることです.フィルタリング、マッピング、遍歴数の削減などの方法があり、これらの方法は2つに分けられます.中間方法と端末方法では、「ストリーム」抽象は生まれつき持続的であり、中間方法は永遠にStreamを返すので、最終結果を取得するには、ストリームによって生成された最終結果を収集するには、エンドポイント操作を使用する必要があります.この2つの方法を区別するのは彼の戻り値を見ることであり,Streamであれば中間方法であり,そうでなければ終点方法である.
次の中間メソッド(filter,map)および終点メソッド(collect,sum)を簡単に紹介します.
3.1.1Filter
データストリームにおけるフィルタリング機能の実現は,まず考えられる最も自然な動作である.Streamインタフェースは、フィルタ条件を定義したlambda式を使用するために動作を表すPredicate実装を受け入れるfilter法を暴露した.

List persons = …
Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//  18     

3.1.2Map
もし私たちが今、オブジェクトを変換するなどのデータをフィルタしたら.Mapオペレーションでは、入力と実行結果をそれぞれ表すFunctionの汎用T(Functionの汎用T、R)を実行できます.これは、パラメータを受け入れて戻ります.まず、匿名の内部クラスでどのように説明するかを見てみましょう.

Stream adult= persons
    .stream()
    .filter(p -> p.getAge() > 18)
    .map(new Function() {
     @Override
     public Adult apply(Person person) {
      return new Adult(person);//   18        
     }
    });

次に、上記の例をlambda式を用いた書き方に変換します.

Stream map = persons.stream()
     .filter(p -> p.getAge() > 18)
     .map(person -> new Adult(person));

3.1.3Count
countメソッドはストリームの終点メソッドであり、ストリームの結果を最終的に統計し、intを返すことができます.例えば、18歳を満たす総人数を計算します.

int countOfAdult=persons.stream()
      .filter(p -> p.getAge() > 18)
      .map(person -> new Adult(person))
      .count();

3.1.4Collect
collectメソッドもストリームの終点メソッドであり,最終的な結果を収集することができる.

List adultList= persons.stream()
      .filter(p -> p.getAge() > 18)
      .map(person -> new Adult(person))
      .collect(Collectors.toList());

または、特定のインプリメンテーションクラスを使用して結果を収集したい場合は、次のようにします.

List adultList = persons
     .stream()
     .filter(p -> p.getAge() > 18)
     .map(person -> new Adult(person))
     .collect(Collectors.toCollection(ArrayList::new));

紙面が限られているので、他の中間方法と終点方法は一つ一つ紹介されていません.上のいくつかの例を見て、この2つの方法の違いを理解すればいいので、後で需要に応じて使用を決めることができます.
3.2シーケンスフローとパラレルフロー
各Streamには、順次実行と並列実行の2つのモードがあります.
シーケンスフロー:

List  people = list.getStream.collect(Collectors.toList());

並列フロー:

List  people = list.getStream.parallel().collect(Collectors.toList());

その名の通り、シーケンス方式で遍歴すると、各itemが読み終わってから次のitemを読みます.パラレルパスを使用すると、配列は複数のセグメントに分割され、それぞれが異なるスレッドで処理され、結果が一緒に出力されます.
3.2.1パラレルフローの原理:

List originalList = someData;
split1 = originalList(0, mid);//       
split2 = originalList(mid,end);
new Runnable(split1.process());//       
new Runnable(split2.process());
List revisedList = split1 + split2;//     

みんなはhadoopに対して少し理解して知っていて、中のMapReduce自身はビッグデータセットを並列に処理するためのソフトウェアのフレームワークで、そのビッグデータを処理する核心思想は大きくて小さくて、異なる機械に割り当ててmapを実行して、最終的にreduceを通じてすべての機械の結果を結びつけて1つの最終結果を得て、MapReduceと異なって、Streamはマルチコア技術を用いてビッグデータをマルチコアで並列処理でき,MapReduceは分散できる.
3.2.2シーケンスとパラレルパフォーマンステストの比較
マルチコアマシンの場合、理論的にパラレルストリームはシーケンスストリームよりも2倍速くなります.次はテストコードです.

long t0 = System.nanoTime();

  //       100    ,   2     ,toArray()     

  int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();

  long t1 = System.nanoTime();

  //       ,          

  int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();

  long t2 = System.nanoTime();

  //       serial: 0.06s, parallel 0.02s,            

  System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);

3.3 Folk/Joinフレームワークについて
アプリケーションハードウェアの並列性はjava 7にあります.それはjavaです.util.concurrentパッケージの新しい機能の一つはfork-joinスタイルの並列分解フレームワークであり、同様に強力で効率的で、興味のある学生が研究しているが、ここでは詳しくは話さない.Streamに比べて.parallel()という方法では、私は後者に傾いています.
4.まとめ
Lambdaがなければ、Streamはかなり違和感があり、上の3.1のような匿名の内部クラスを大量に生成します.2 mapの例では、default methodがなければ、集合フレームワークの変更は必ず大量の変更を引き起こすので、lambda+default methodはjdkライブラリをより強くし、柔軟さ、Stream、集合フレームワークの改善が最善の証明です.