ラムダが Java にクロージャの概念をもたらすのを長い間待っていましたが、コレクションでラムダを使用しない場合、多くの価値を失います。既存のインターフェイスをラムダ スタイルに移行するという問題は、デフォルトのメソッドによって解決されました。この記事では、Java コレクションのバッチ データ操作を深く分析し、ラムダの最も強力な効果の謎を解明します。
ラムダが Java にクロージャの概念をもたらすのを長い間待っていましたが、コレクションでラムダを使用しなければ、多くの価値が失われます。既存のインターフェイスをラムダ スタイルに移行するという問題は、デフォルトのメソッドによって解決されました。この記事では、Java コレクションのバルク データ操作 (バルク オペレーション) を深く分析し、ラムダの最も強力な役割の謎を解明します。
1. JSR335について
JSRはJava Specific Requestsの略で、Java仕様リクエストを意味します。Java 8バージョンの主な改良点は、Javaを作成することを目的としたLambdaプロジェクト(JSR 335)です。マルチコアの方が簡単 プロセッサがコードを記述します。
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 ライブラリがループを制御します。各人物オブジェクトに姓がどのように設定されるかを気にする必要はありません。ライブラリは、実行環境、並列読み込み、順不同読み込み、または遅延読み込みに応じて、その設定方法を決定できます。これは内部反復であり、クライアントは動作 p.setLastName をデータとして API に渡します。実際、内部反復はコレクションのバッチ操作と密接な関係はありませんが、そのおかげで文法表現の変化を感じることができます。バッチ操作に関連して本当に興味深いのは、新しいストリーム API です。新しい java.util.stream パッケージが JDK 8 に追加されました。
3. ストリーム API
ストリームはデータ ストリームのみを表し、データ構造を持たないため、一度通過した後は通過することはできません (これは、コレクションとは異なり、プログラミング時に注意する必要があります)何度走査されても、データはまだ存在します)、そのソースはコレクション、配列、io などです。
3.1 中間メソッドと終了メソッド
フロー機能は、ビッグデータを操作するためのインターフェースを提供し、データ操作をより簡単かつ高速にします。フィルタリング、マッピング、トラバーサル数の削減などのメソッドがあり、これらのメソッドは中間メソッドと終端メソッドの 2 つのタイプに分けられます。中間メソッドは本質的に連続的なものである必要があります。最終結果を取得したい場合は、エンドポイント操作を使用して、ストリームによって生成された最終結果を収集する必要があります。これら 2 つのメソッドの違いは、戻り値に注目することです。ストリームの場合は中間メソッドであり、そうでない場合は終了メソッドです。
いくつかの中間メソッド (フィルター、マップ) とエンドポイント メソッド (収集、合計) の簡単な紹介
3.1.1フィルター
データ ストリームでのフィルター関数の実装は、最も自然な操作です。まず考えてください。 Stream インターフェイスは、フィルター条件を定義するラムダ式 を使用する操作を表す Predicate 実装を受け入れることができるフィルター メソッドを公開します。
List persons = … Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//过滤18岁以上的人
3.1.2Map
Stream adult= persons .stream() .filter(p -> p.getAge() > 18) .map(new Function() { @Override public Adult apply(Person person) { return new Adult(person);//将大于18岁的人转为成年人 } });次に、上記の例をラムダ式に変換します:
Stream map = persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person));
3.1.3Count
int countOfAdult=persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .count();
3.1.4Collect
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));
篇幅有限,其他的中间方法和终点方法就不一一介绍了,看了上面几个例子,大家明白这两种方法的区别即可,后面可根据需求来决定使用。
3.2顺序流与并行流
每个Stream都有两种模式:顺序执行和并行执行。
顺序流:
List <Person> people = list.getStream.collect(Collectors.toList());
并行流:
List <Person> 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将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。
3.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.2map例子,如果没有default method,集合框架更改势必会引起大量的改动,所以lambda+default method使得jdk库更加强大,以及灵活,Stream以及集合框架的改进便是最好的证明。
以上がJava8 の新機能を解釈する - ラムダの役割の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。