構文の説明
ラムダ式は次の部分で構成されます:
1. かっこ内の仮パラメータのカンマ区切りのリスト。 CheckPersonal.test メソッドには、Person クラスのインスタンスを表すパラメーター p が含まれています。注: ラムダ式のパラメータの型は省略できます。また、パラメータが 1 つしかない場合は、括弧も省略できます。たとえば、前のセクションで説明したコード:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
2. 矢印記号: ->。パラメータと関数本体を分離するために使用されます。
3. 関数本体。式またはコードのブロックで構成されます。前のセクションの例では、次のような式が使用されました:
p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
式が使用される場合、Java ランタイムは式の値を計算して返します。さらに、コード ブロックで return ステートメントを使用することも選択できます:
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
ただし、return ステートメントは式ではありません。ラムダ式ではステートメントを中括弧で囲む必要がありますが、null 値を返すメソッドを呼び出すだけの場合は中括弧で囲む必要はないため、次の記述も正しいです:
email -> System.out.println(email)
lambda式 これはメソッド宣言と非常によく似ています。したがって、ラムダ式は匿名メソッド、つまり名前が定義されていないメソッドとみなすこともできます。
上記のラムダ式はすべて、仮パラメータとしてパラメータを 1 つだけ使用する式です。次のインスタンス クラス Caulator は、複数のパラメーターを仮パラメーターとして使用する方法を示しています。
package com.zhyea.zytools; public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; IntegerMath subtraction = (a, b) -> a - b; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } }
コード内の OperatorBinary メソッドは、2 つの整数パラメーターを使用して算術演算を実行します。ここでの算術演算自体は、IntegerMath インターフェイスのインスタンスです。上記のプログラムでは、ラムダ式を使用して加算と減算の 2 つの算術演算が定義されています。実行プログラムは次の内容を出力します:
40 + 2 = 42 20 - 10 = 10
外部クラスのローカル変数へのアクセス
ローカル クラスや匿名クラスと同様に、ラムダ式も外部クラスのローカル変数にアクセスできます。違いは、ラムダ式を使用するときに上書きなどの問題を考慮する必要がないことです。ラムダ式は単なる語彙概念です。つまり、スーパークラスから名前を継承する必要も、新しいスコープを導入する必要もありません。つまり、ラムダ式内の宣言は、その外部環境での宣言と同じ意味を持ちます。これを次の例で示します。
package com.zhyea.zytools; import java.util.function.Consumer; public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { //如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final” // x = 99; Consumer<integer> myConsumer = (y) ->{ System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); }; myConsumer.accept(x); } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
このコードは次を出力します。
x = 23 y = 23 this.x = 1 LambdaScopeTest.this.x = 0
この例のラムダ式 myConsumer のパラメータ y が x に置き換えられると、コンパイラは次のようになります。エラーを報告します:
Consumer<integer> myConsumer = (x) ->{ // .... };
コンパイラ エラー メッセージは次のとおりです: 「変数 x はメソッド MethodInFirstLevel(int) ですでに定義されています」。これは、変数 x がメソッド MethodInFirstLevel で定義されていることを意味します。ラムダ式では新しいスコープが導入されないため、このエラーが報告されます。したがって、ラムダ式で外部クラスのフィールド、メソッド、仮パラメータに直接アクセスできます。この例では、ラムダ式 myConsumer は、methodInFirstLevel メソッドの仮パラメータ x に直接アクセスします。外部クラスのメンバーにアクセスするときは、this キーワードを直接使用することもできます。この例では、this.x は FirstLevel.x を指します。
ただし、ローカル クラスや匿名クラスと同様に、ラムダ式はローカル変数またはfinal (またはfinalと同等) として宣言された外部メンバーにのみアクセスできます。たとえば、サンプルコードのmethodInFirstLevelメソッドの「x=99」の前のコメントを削除します:
//如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final” x = 99; Consumer<integer> myConsumer = (y) ->{ System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); };
このステートメントではパラメータxの値が変更されているため、methodInFirstLevelのパラメータxは使用できなくなります。最終的なスタイルとみなされます。したがって、Java コンパイラは、ラムダ式がローカル変数 x にアクセスする場合、「ラムダ式から参照されるローカル変数は Final または実質的に Final である必要があります」のようなエラーを報告します。
ターゲットの型
ラムダ式の型を決定する方法。兵役年齢の職員をスクリーニングするためのコードを見てみましょう:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
このコードは 2 つの場所で使用されています:
public static void printpersons(List0833d296851a1e081f2175a4cbfa7135 roster, Checkperson tester) - オプション 3
public void printPersonsWithPredicate(List8abf60ac54173a2785e603c7a1f95b4e roster, Predicate8abf60ac54173a2785e603c7a1f95b4e tester) - オプション 6
printPersons メソッドを呼び出すとき、このメソッドは Checkperson 型のパラメータを期待します。このとき、上記の式は Checkperson 型の式です。 。 printPersonsWithPredicate メソッドを呼び出すときは、Predicate8abf60ac54173a2785e603c7a1f95b4e 型のパラメーターが予期され、同じ式の型は Predicate8abf60ac54173a2785e603c7a1f95b4e になります。このように、メソッドが期待する型によって決まる型をターゲット型と呼びます(実際には、ここでは Scala の型推論の方が適切だと思います)。 Java コンパイラは、ターゲット型のコンテキストまたはラムダ式が見つかった位置を通じてラムダ式の型を決定します。これは、ラムダ式は、Java コンパイラが型を推論できる場合にのみ使用できることを意味します。 ;
条件式 (?:);
例外がスローされた場合。
ターゲットの型とメソッドのパラメータ对于方法参数,Java编译器还需要依赖两个语言特性来决定目标类型:重载解析和类型参数推断。
看一下下面的这两个函数式接口( java.lang.Runnable and java.util.concurrent.Callabled94943c0b4933ad8cac500132f64757f):
public interface Runnable { void run(); } public interface Callable<v> { V call(); }
Runnable.run()方法没有返回值,而Callable.call()方法有。
假设我们像下面这样重载了invoke方法:
void invoke(Runnable r) { r.run(); } <t> T invoke(Callable<t> c) { return c.call(); }
那么在下面的语句中将会调用哪个方法呢:
String s = invoke(() -> "done");
调用的是invoke(Callable8742468051c85b06f0a0af9e3e506b5c),因为这个方法有返回值,而invoke(Runnable8742468051c85b06f0a0af9e3e506b5c)没有返回值。在这种情况下lambda表达式(() -> “done”)的类型是Callable8742468051c85b06f0a0af9e3e506b5c。
序列化
如果一个lambda表达式的目标类型还有它调用的参数的类型都是可序列化的,那么lambda表达式也是可序列化的。然而就像内部类一样,强烈不建议对lambda表达式进行序列化。
更多java中lambda表达式语法说明相关文章请关注PHP中文网!