ラムダの紹介
Lambda式は、Java SE 8の重要な新機能です。LambdaExpressionsを使用すると、式で機能的なインターフェイスを置き換えることができます。 Lambda式は、これらのパラメーターを使用する通常のパラメーターリストとボディ(式またはコードブロックになる可能性がある本体)を提供するメソッドのようなものです。
ラムダの表現は、コレクションライブラリも強化します。 Java SE 8は、収集データのバッチ操作を操作する2つのパッケージを追加します: java.util.functionパッケージとjava.util.streamパッケージ。ストリームはイテレーターのようなものですが、多くの追加機能が添付されています。一般に、Java言語がジェネリックと注釈を追加するため、ラムダの表現とストリームは最大の変化です。
Lambda式は本質的に匿名の方法であり、その基礎となる層は、匿名クラスを生成するためにinvokedynamic指令を通じて実装されます。よりシンプルな構文と書き込み方法を提供し、機能的なインターフェイスを式に置き換えることができます。一部の人々の目には、ラムダはあなたのコードをより簡潔にし、まったく使用しないことができます - この見解は確かに大丈夫ですが、LambdaがJavaに閉鎖をもたらすことです。ラムドバのコレクションに対するサポートのおかげで、ラムダはマルチコアプロセッサ条件下でコレクションを横断するときにパフォーマンスを大幅に改善しました。さらに、データストリームの形でコレクションを処理できます。これは非常に魅力的です。
Lambda構文
Lambdaの構文は非常にシンプルで、次の構造と同様です。
(パラメーター) - >式
または
(パラメーター) - > {ステートメント; }ラムダ式は3つの部分で構成されています。
1。パラメート:同様の方法での正式なパラメーターのリスト、ここでのパラメーターは機能界面のパラメーターです。ここのパラメータータイプは、明示的に宣言されるか、宣言されていないが、JVMによって暗黙的に推測されることができます。さらに、推論タイプが1つしかない場合、括弧は省略できます。
2。->: 「使用されている」と理解できます
3。メソッド本体:式またはコードブロックである可能性があります。これは、機能インターフェイスでのメソッドの実装です。コードブロックは、値を返すことも、何も逆転しません。ここのコードブロックは、メソッドのメソッド本体に相当します。それが式の場合は、値を返したり、何も返したりすることもできます。
次の例を使用して説明しましょう。
//例1:パラメーターを受け入れる必要はありません。直接10() - > 10 //例2:intタイプの2つのパラメーターを受け入れ、これら2つのパラメーターの合計(int x、int y) - > x+y; //例2:xとyの2つのパラメーターを受け入れます。文字列を受け入れて文字列をプリントして制御します。結果(文字列名) - > system.out.println(name); //例4:推定されたタイプパラメーター名を受け入れ、文字列をコンソールに印刷し、name-> system.out.println(name) sex) - > {system.out.println(name); system.out.println(sex)} //例6:パラメーターxを受け入れ、パラメーターx-> 2*xの2倍を返すラムダの使用場所
[functional interface] [1]では、ラムダ式のターゲットタイプが機能的インターフェイスであることがわかります。各ラムダは特定の機能インターフェイスを介して特定のタイプと一致させることができます。したがって、ラムダ式は、ターゲットタイプに一致するどこにでも適用できます。 Lambda式は、関数インターフェイスの抽象関数の説明と同じパラメータータイプを持つ必要があります。また、その戻り型は、抽象関数の返品タイプと互換性がなければならず、投げることができる例外は関数の説明範囲に限定されます。
次に、カスタム機能インターフェイスの例を見てみましょう。
@functionalinterfaceインターフェイスコンバーター<f、t> {t convert(f from);}まず、インターフェイスを従来の方法で使用します。
converter <string、integer> converter = new converter <string、integer>(){@override public integer convert(string from){return integer.valueof(from); }}; integer result = converter.convert( "200"); system.out.println(result);明らかにこれに問題はないので、次のことはラムダがフィールドに来て、ラムダを使用してコンバーターインターフェイスを実装する瞬間です。
converter <string、integer> converter =(param) - > integer.valueof(param); integer result = converter.convert( "101"); system.out.println(result);
上記の例を通して、あなたはラムダの使用を簡単に理解していると思います。以下では、一般的に使用される実行可能を使用してデモを行います。
過去には、このコードを書いていたかもしれません。
新しいスレッド(new runnable(){@override public void run(){system.out.println( "hello lambda");}})。start();場合によっては、多数の匿名クラスがコードを乱雑に見せることができます。これで、ラムダを使用してシンプルにすることができます。
新しいスレッド(() - > system.out.println( "hello lambda"))。start();
メソッド参照
メソッド参照は、ラムダ式を書くための簡略化された方法です。参照される方法は、実際にはラムダ式のメソッド本体の実装であり、その構文構造は次のとおりです。
ObjectRef :: MethodName
左側はクラス名またはインスタンス名、中央はメソッド参照記号 "::"であり、右側は対応するメソッド名です。
メソッド参照は、次の3つのカテゴリに分けられます。
1。静的メソッド参照
場合によっては、次のようなコードを書くことがあります。
public class referenceTest {public static void main(string [] args){converter <string、integer> converter = new converter <string、integer>(){@override public integer convert(string from){return referencetest.string2int(from); }}; converter.convert( "120"); } @functionalinterfaceインターフェイスコンバーター<f、t> {t convert(f from); } static int string2int(string from){return integer.valueof(from); }}現時点では、静的参照を使用する場合、コードはより簡潔になります。
converter <string、integer> converter = referencetest :: string2int; converter.convert( "120");
2。インスタンスメソッド参照
また、このようなコードを書くこともできます。
public class referenceTest {public static void main(string [] args){converter <string、integer> converter = new converter <string、integer>(){@override public integer convert(string from){return new helper()。string2int(from); }}; converter.convert( "120"); } @functionalinterfaceインターフェイスコンバーター<f、t> {t convert(f from); } staticクラスヘルパー{public int string2int(string from){return integer.valueof(from); }}}また、サンプルメソッドを使用して参照すると、より簡潔に表示されます。
ヘルパーヘルパー= new Helper(); converter <string、integer> converter = helper :: string2int; converter.convert( "120");
3。コンストラクターメソッド参照
次に、コンストラクターへの参照を示しましょう。最初に、親クラスの動物を定義します。
class animal {private string name;プライベートインクエイジ; public Animal(string name、int age){this.name = name; this.age = age; } public void Behavior(){}}次に、私たちは動物の2つのサブクラスを定義しています: Dog、Bird
パブリッククラスの鳥は動物を拡張します{public bird(string name、int age){super(name、age); } @Override public void Behavior(){system.out.println( "fly"); }} class Dogは動物を拡張します{public dog(string name、int age){super(name、age); } @Override public void Behavior(){system.out.println( "run"); }}次に、工場インターフェイスを定義します。
インターフェイスファクトリー<t拡張動物> {t create(string name、int age); }次に、従来の方法を使用して、犬と鳥のクラスのオブジェクトを作成します。
Factory Factory = new Factory(){@Override Public Animal Create(String name、int age){return new Dog(name、age); }}; Factory.Create( "Alias"、3); Factory = new Factory(){@Override Public Animal Create(String name、int age){return new Bird(name、age); }}; Factory.create( "Smook"、2);2つのオブジェクトを作成するためだけに10以上のコードを書きました。次に、コンストラクターリファレンスを使用してみましょう。
Factory <Animal> dogactory = dog :: new; Animal Dog = DogFactory.Create( "Alias"、4); Factory <Bird> BirdFactory = Bird :: new; Bird bird = Birdfactory.create( "Smook"、3);
このようにして、コードはきれいできれいに見えます。 Dog::newオブジェクトを貫通する場合は、 Factory.create関数に署名して、対応する作成機能を選択します。
Lambdaのドメインとアクセス制限
ドメインはスコープであり、ラムダ式のパラメーターリストのパラメーターは、ラムダ式(ドメイン)の範囲内で有効です。 Lambda式では、外部変数にアクセスできます。ローカル変数、クラス変数、静的変数ですが、操作制限の程度は異なります。
ローカル変数にアクセスします
Lambda式の外側の局所変数は、JVMによって最終型に暗黙的にコンパイルされるため、アクセスするだけではありませんが、変更できません。
public class referenceTEST {public static void main(string [] args){int n = 3;計算= param-> {// n = 10;エラーをコンパイルしてn + paramをコンパイルします。 }; calculate.calculate(10); } @functionalinterface interface calculate {int calculate(int value); }}静的変数とメンバー変数にアクセスします
Lambda式の内部では、静的変数とメンバー変数が読みやすく書かれています。
public class referenceTETEST {public int count = 1; public static int num = 2; public void test(){計算= param-> {num = 10; //静的変数count = 3; //メンバー変数return n + paramを変更する}; calculate.calculate(10); } public static void main(string [] args){} @functionalinterface interface calculate {int calculate(int value); }}Lambdaは、機能インターフェイスのデフォルトの方法にアクセスできません
Java8は、デフォルトのキーワード定義をインターフェイスに追加できるデフォルトメソッドを含むインターフェイスを強化します。ここでは、デフォルトのメソッドへのアクセスは内部的にはサポートされていないことに注意する必要があります。
ラムダの練習
[functional interface] [2]セクションでは、多くの機能インターフェイスがjava.util.functionパッケージに組み込まれていることを述べ、一般的に使用される機能インターフェイスについて説明します。
述語インターフェイス
パラメーターを入力して、論理判断のための多くのデフォルトの方法を含むBoolean値を返します。
@test public void predicttest(){predicate <string> predict =(s) - > s.length()> 0;ブールテスト= Predict.Test( "Test"); System.out.println( "文字列の長さは0より大きい:" +テスト); test = predict.test( ""); System.out.println( "文字列の長さは0より大きい:" +テスト);述語<Object> pre = objects :: nonnull;オブジェクトob = null; test = pre.test(ob); system.out.println( "オブジェクトは空になりません:" +テスト); ob = new object(); test = pre.test(ob); system.out.println( "オブジェクトは空になりません:" +テスト); }関数インターフェイス
パラメーターを受信し、単一の結果を返します。デフォルトのメソッド( andThen )は、複数の関数を一緒に文字列並べて、コンポジットFuntion (入力、出力)の結果を形成できます。
@test public void functionTest(){function <string、integer> tointeger = integer :: valueof; // tointegerの実行結果は、2番目のバックトンストリング関数<string、string> backtostring = tointeger.andthen(string :: valueof)への入力として使用されます。文字列result = backtostring.apply( "1234"); system.out.println(result); function <integer、integer> add =(i) - > {system.out.println( "frist input:" + i); I * 2を返します。 }; function <integer、integer> zero = add.andthen((i) - > {system.out.println( "second input:" + i); return i * 0;});整数res = zero.Apply(8); System.out.println(res); }サプライヤーインターフェイス
特定のタイプの結果を返します。 Functionとは異なり、 Supplierパラメーター(サプライヤー、出力を伴うが入力なし)を受け入れる必要はありません
@test public void suppliertest(){supplier <string> supplier =() - > "Special Type Value";文字列s = supplier.get(); System.out.println(s); }消費者インターフェイス
単一の入力パラメーターで実行する必要がある操作を表します。 Functionとは異なり、 Consumer価値を返しません(消費者、入力、出力なし)
@test public void consumertest(){consumer <integer> add5 =(p) - > {system.out.println( "old value:" + p); p = p + 5; System.out.println( "new Value:" + P); }; add5.accept(10); }上記の4つのインターフェイスの使用は、 java.util.functionパッケージの4つのタイプを表します。これらの4つの機能インターフェイスを理解した後、他のインターフェイスは理解しやすくなります。それでは、簡単な要約を作成しましょう。
Predicate論理的判断に使用され、 Function入力と出力がある場所で使用され、 Supplier入力と出力がない場所で使用され、 Consumer入力があり、出力がない場所で使用されます。その名前の意味に基づいた使用シナリオを知ることができます。
ストリーム
LambdaはJava 8に閉鎖をもたらします。これは、収集操作に特に重要です。Java8は、コレクションオブジェクトのストリームでの機能操作をサポートしています。さらに、Stream APIはコレクションAPIにも統合されており、コレクションオブジェクトのバッチ操作が可能になります。
ストリームを知りましょう。
ストリームはデータストリームを表します。データ構造はなく、要素自体を保存しません。その操作はソースストリームを変更することはありませんが、新しいストリームを生成します。操作データのインターフェイスとして、フィルタリング、ソート、マッピング、および規制を提供します。これらのメソッドは、返品タイプに従って2つのカテゴリに分割されます。ストリームタイプを返す方法は中間メソッド(中間操作)と呼ばれ、残りは完了方法(完全操作)です。完了方法は何らかのタイプの値を返し、中間メソッドは新しいストリームを返します。通常、中間法の呼び出しはチェーンされ、プロセスはパイプラインを形成します。最終的な方法が呼び出されると、値がパイプラインからすぐに消費されます。ここで覚えておく必要があります:ストリーム操作は可能な限り「遅延」として実行されます。これは、リソースの使用量を削減し、パフォーマンスを向上させるのに役立つ「怠zyな操作」と呼ばれるものです。すべての中間操作(ソートを除く)では、遅延モードで実行されます。
Streamは、強力なデータ操作機能を提供するだけでなく、さらに重要なことに、Streamはシリアルと並列性の両方をサポートすることです。並列処理により、ストリームはマルチコアプロセッサでパフォーマンスを向上させることができます。
ストリームの使用プロセスには固定パターンがあります。
1.ストリームを作成します
2。中間操作により、元のストリームを「変更」して新しいストリームを生成します
3.完了操作を使用して最終結果を生成します
つまり
Create-> Change-> Complete
ストリームの作成
コレクションの場合、コレクションのstream()またはparallelStream()を呼び出すことで作成できます。さらに、これら2つの方法もコレクションインターフェイスに実装されています。配列の場合、Streamの静的方法of(T … values)によって作成できます。さらに、アレイはストリームのサポートも提供します。
上記のコレクションまたは配列に基づいてストリームを作成することに加えて、 Steam.empty()を介して空のストリームを作成するか、Stream's generate()を使用して無限のストリームを作成することもできます。
シリアルストリームを例として、一般的に使用されるいくつかの中間および完了方法を説明しましょう。最初にリストコレクションを作成します。
リスト<文字列>リスト= new ArrayList <String>(); lists.add( "a1"); lists.add( "a2"); lists.add( "b1"); lists.add( "b2"); lists.add( "b3"); lists.add( "o1");
中間法
フィルター
Predicateインターフェイスと組み合わせて、フィルターはストリーミングオブジェクトのすべての要素をフィルターします。この操作は中間操作です。つまり、操作によって返された結果に基づいて他の操作を実行できます。
public static void streamfiltertest(){lists.stream()。フィルター((s-> s.startswith( "a")))。foreach(system.out :: println); //上記の操作に相当predicate <string> predicate =(s) - > s.startswith( "a"); lists.stream()。filter(predicate).foreach(system.out :: println); //連続フィルタリングPredicate <String> Predicate1 =(S-> S.Endswith( "1")); lists.stream()。フィルター(Predicate).filter(predicate1).foreach(system.out :: println); }ソート(ソート)
コンパレータインターフェイスと組み合わせると、この操作はソートされたストリームのビューを返し、元のストリームの順序は変更されません。照合ルールはコンパレータを通じて指定され、デフォルトは自然な順序で並べ替えることです。
public static void streamsortedtest(){system.out.println( "default comparator"); lists.stream()。sorted()。filter((s-> s.startswith( "a")))。foreach(system.out :: println); system.out.println( "Custom Comparator"); lists.stream()。sorted((p1、p2) - > p2.compareto(p1))。フィルター((s-> s.startswith( "a")))。foreach(system.out :: println); }マップ(マップ)
Functionインターフェイスと組み合わせると、この操作は、ストリームオブジェクトの各要素を別の要素にマッピングし、要素タイプの変換を実現できます。
public static void streammaptest(){lists.stream()。map(string :: touppercase).sorted((a、b) - > b.compareto(a))。foreach(system.out :: println); System.out.println( "カスタムマッピングルール"); function <string、string> function =(p) - > {return p + ".txt"; }; lists.stream()。map(string :: touppercase).map(function).sorted((a、b) - > b.compareto(a))。foreach(system.out :: println); }上記では、一般的に使用される3つの操作を簡単に導入します。これにより、コレクションの処理が大幅に簡素化されます。次に、完了するいくつかの方法を紹介します。
仕上げ方法
「変換」プロセスの後、結果を取得する必要があります。つまり、操作が完了します。以下の関連操作を見てみましょう。
マッチ
predicateストリームオブジェクトと一致し、最終的にBoolean型の結果を返すかどうかを判断するために使用されます。
public static void streammatchtest(){//ストリームオブジェクトの1つの要素がboolean anystartwitha = lists.stream()。anymatch((s-> s.startswith( "a")))に一致する限り、trueを返します。 system.out.println(anystartwitha); //ストリームオブジェクトの各要素がboolean allstartwitha = lists.stream()。allmatch((s-> s.startswith( "a")))に一致する場合にtrueを返します。 System.out.println(allstartwitha); }集める
変換後、これらの要素をコレクションに保存するなど、変換されたストリームの要素を収集します。現時点では、Streamが提供する収集方法を使用できます。たとえば、:
public static void streamcollecttest(){list <string> list = lists.stream()。フィルター((p) - > p.startswith( "a"))。sorted()。 System.out.println(list); }カウント
SQLのようなカウントは、ストリーム内の要素の総数をカウントするために使用されます。たとえば、:
public static void streamcounttest(){long count = lists.stream()。filter((s-> s.startswith( "a")))。count(); System.out.println(count); }減らす
reduceメソッドにより、独自の方法で要素を計算したり、ストリームの要素を何らかのパターンで関連付けることができます。たとえば
public static void streameducetest(){optional <string> optional = lists.stream()。sorted()。resid((s1、s2) - > {system.out.println(s1 + "|" + s2); return s1 + "|" + s2;}); }実行結果は次のとおりです。
A1 | A2A1 | A2 | B1A1 | A2 | B1 | B2A1 | A2 | B1 | B2 | B3A1 | A2 | B1 | B2 | B3 | O1
平行ストリームとシリアルストリーム
これまでに、一般的に使用される中間および完了した操作を導入しています。もちろん、すべての例はシリアルストリームに基づいています。次に、キードラマ - パラレルストリーム(パラレルストリーム)を紹介します。パラレルストリームは、フォークに結合する並列分解フレームワークに基づいて実装され、ビッグデータセットを複数の小さなデータに分割し、処理のために異なるスレッドに渡します。このようにして、マルチコア処理の状況ではパフォーマンスが大幅に改善されます。これは、MapReduceの設計概念と一致しています。大きなタスクが小さくなり、小さなタスクが実行のために異なるマシンに再割り当てされます。しかし、ここの小さなタスクは異なるプロセッサに引き渡されます。
parallelStream()を介して平行ストリームを作成します。並列ストリームがパフォーマンスを本当に改善できるかどうかを確認するために、次のテストコードを実行します。
最初に大きなコレクションを作成します:
List <String> BigLists = new ArrayList <>(); for(int i = 0; i <10000000; i ++){uuid uuid = uuid.randomuuid(); biglists.add(uuid.tostring()); }シリアルストリームの下でソートする時間をテストします。
private static void notparallelStreamSortedTest(List <String> BigLists){long starttime = System.nanotime(); long count = biglists.stream()。sorted()。count(); long endtime = system.nanotime(); long millis = timeunit.nanoseconds.tomillis(終了時期 - 開始時刻); system.out.println(system.out.printf( "serial sort:%d ms"、millis)); }並列ストリームでソートする時間をテストします。
private static void ParallelStreamSortedTest(List <String> BigLists){long starttime = system.nanotime(); long count = biglists.parallelstream()。sorted()。count(); long endtime = system.nanotime(); long millis = timeunit.nanoseconds.tomillis(終了時期 - 開始時刻); system.out.println(system.out.printf( "Parallelsorting:%d ms"、millis)); }結果は次のとおりです。
シリアルソート:13336ミリ秒
並列ソート:6755 ms
これを見た後、パフォーマンスが約50%向上したことがわかりました。また、将来parallel Streamを使用できると思いますか?実際、そうではありません。現在もシングルコアプロセッサであり、データボリュームが大きくない場合、シリアルストリーミングは依然として非常に良い選択です。また、場合によっては、シリアルストリームのパフォーマンスが優れていることがわかります。特定の用途については、最初にテストしてから、実際のシナリオに従って決定する必要があります。
怠zyな操作
上記では、ストリームが可能な限り遅れて実行されることについて説明しました。ここでは、無限のストリームを作成して説明します。
まず、Stream generateメソッドを使用して自然数シーケンスを作成し、次にmapを介してストリームを変換します。
//インクリメンタルシーケンスクラスNatureseqを実装したサプライヤー<long> {long value = 0; @Override public long get(){value ++;返品値。 }} public void streamcreateTest(){stream <long> stream = stream.generate(new natureseq()); system.out.println( "要素数:"+stream.map((param) - > {return param;})。limit(1000).count()); }実行結果は次のとおりです。
要素数:1000
最初は、中間操作( filter,mapなどなど、 sortedれていない)が問題であることがわかりました。つまり、ストリームで中間操作を実行し、新しいストリームを生き抜くプロセスはすぐに有効になりません(または、この例のmap操作は永久に実行され、ブロックされます)。 limit()メソッドを使用して、この無限のストリームを有限ストリームに変換します。
要約します
上記は、Java Lambdaの簡単な紹介のすべての内容です。この記事を読んだ後、Java Lambdaをより深く理解していますか?この記事がJava Lambdaを学ぶのに役立つことを願っています。