この記事は誰に向けたものですか?
この資料には、新規性はおろか、学術的価値があるとは主張されていません。全く逆です。私は、(一部の人にとっては) 複雑なことをできるだけ簡単に説明しようとします。Stream API について説明してほしいというリクエストが、これを書くきっかけになりました。私はそれについて考え、ラムダ式を理解していないとストリームの例の一部は理解できないと判断しました。それでは、ラムダ式から始めます。
- この記事の最初の部分を読んだ人を対象としています。
- これは、Java Core についてはすでによく知っているが、Java のラムダ式についてはまったく知らない人を対象としています。あるいは、ラムダ式について聞いたことがあるかもしれませんが、詳細は不明です。
- これは、ラムダ式をある程度理解しているものの、ラムダ式にまだ気が遠く、使用に慣れていない人を対象としています。
この資料には、新規性はおろか、学術的価値があるとは主張されていません。全く逆です。私は、(一部の人にとっては) 複雑なことをできるだけ簡単に説明しようとします。Stream API について説明してほしいというリクエストが、これを書くきっかけになりました。私はそれについて考え、ラムダ式を理解していないとストリームの例の一部は理解できないと判断しました。それでは、ラムダ式から始めます。
外部変数へのアクセス
このコードは匿名クラスでコンパイルされますか?
int counter = 0;
Runnable r = new Runnable() {
@Override
public void run() {
counter++;
}
};
いいえ。counter 変数は でなければなりませんfinal。そうでない場合はfinal、少なくともその値を変更することはできません。同じ原則がラムダ式にも当てはまります。これらは、宣言された場所から「参照できる」すべての変数にアクセスできます。ただし、ラムダはそれらを変更してはなりません (新しい値を割り当ててはいけません)。ただし、匿名クラスでこの制限を回避する方法があります。参照変数を作成し、オブジェクトの内部状態を変更するだけです。その際、変数自体は変更されず (同じオブジェクトを指す)、安全に としてマークできますfinal。
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
}
};
ここでのcounter変数はオブジェクトへの参照ですAtomicInteger。そして、incrementAndGet()このメソッドは、このオブジェクトの状態を変更するために使用されます。変数自体の値は、プログラムの実行中に変更されません。これは常に同じオブジェクトを指すため、final キーワードを使用して変数を宣言できます。以下に同じ例を示しますが、ラムダ式を使用しています。
int counter = 0;
Runnable r = () -> counter++;
これは、匿名クラスを使用したバージョンと同じ理由でコンパイルされません。 counterプログラムの実行中に変更してはなりません。しかし、次のようにすればすべてうまくいきます。
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
これはメソッドの呼び出しにも当てはまります。ラムダ式内では、すべての「表示」変数にアクセスできるだけでなく、アクセス可能なメソッドを呼び出すこともできます。
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> staticMethod();
new Thread(runnable).start();
}
private static void staticMethod() {
System.out.println("I'm staticMethod(), and someone just called me!");
}
}
プライベートではありますがstaticMethod()、メソッド内でアクセスできるためmain()、メソッド内で作成したラムダ内から呼び出すこともできますmain。
ラムダ式はいつ実行されますか?
次の質問は単純すぎると思われるかもしれませんが、同じように尋ねるべきです。ラムダ式内のコードはいつ実行されますか? いつ作成されますか? それともいつ呼び出されるのか(まだ不明ですが)?これはかなり簡単に確認できます。
System.out.println("Program start");
// All sorts of code here
// ...
System.out.println("Before lambda declaration");
Runnable runnable = () -> System.out.println("I'm a lambda!");
System.out.println("After lambda declaration");
// All sorts of other code here
// ...
System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start();
画面出力:
Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
ラムダ式は、スレッドの作成後、プログラムの実行がメソッドに到達したときのみ、最後に実行されたことがわかりますrun()。確かにそれが宣言されたときはそうではありません。ラムダ式を宣言することで、Runnableオブジェクトを作成し、そのrun()メソッドがどのように動作するかを説明しただけになります。メソッド自体はかなり後で実行されます。
メソッドのリファレンス?
メソッド参照はラムダとは直接関係ありませんが、この記事でメソッド参照について少し述べておくことは意味があると思います。特別なことは何も行わず、単にメソッドを呼び出すだけのラムダ式があるとします。
x -> System.out.println(x)
xいくつかの呼び出しを 受信しSystem.out.println()、呼び出しを渡しますx。この場合、目的のメソッドへの参照に置き換えることができます。このような:
System.out::println
そうです、最後に括弧はありません。より完全な例を次に示します。
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(x -> System.out.println(x));
最後の行では、インターフェイスforEach()を実装するオブジェクトを受け取るメソッドを使用しますConsumer。繰り返しますが、これは関数型インターフェイスであり、void accept(T t)メソッドが 1 つだけあります。したがって、パラメーターを 1 つ持つラムダ式を作成します (インターフェイス自体に型指定されるため、パラメーターの型は指定しません。それを呼び出すことを示すだけですx)。ラムダ式の本体には、accept()メソッドが呼び出されたときに実行されるコードを記述します。ここでは、変数に何が入ったかを単純に表示しますx。この同じforEach()メソッドは、コレクション内のすべての要素を反復処理し、accept()その実装のメソッドを呼び出します。Consumerインターフェイス (ラムダ)、コレクション内の各項目を渡します。先ほど述べたように、このようなラムダ式 (単に異なるメソッドをクラス化するもの) を、目的のメソッドへの参照に置き換えることができます。コードは次のようになります。
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(System.out::println);
println()主なことは、とメソッド のパラメータがaccept()一致していることです。このprintln()メソッドは何でも受け入れることができるため (すべてのプリミティブ型とすべてのオブジェクトに対してオーバーロードされています)、ラムダ式の代わりにメソッドへの参照を に渡すだけで済みprintln()ますforEach()。次に、forEach()コレクション内の各要素を取得し、それをprintln()メソッドに直接渡します。初めてこの問題に遭遇した人は、電話をかけているわけではないことに注意してくださいSystem.out.println()(単語の間にドットがあり、最後に括弧が付いています)。代わりに、このメソッドへの参照を渡します。これを書くと
strings.forEach(System.out.println());
コンパイルエラーが発生します。を呼び出す前にforEach()、Java は がSystem.out.println()呼び出されていることを確認するため、戻り値が であることを理解し、代わりにオブジェクトを期待しているにvoid渡そうとします。 voidforEach()Consumer
メソッド参照の構文
それは非常に簡単です:-
次のように静的メソッドへの参照を渡します。
ClassName::staticMethodNamepublic class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Dota"); strings.add("GTA5"); strings.add("Halo"); strings.forEach(Main::staticMethod); } private static void staticMethod(String s) { // Do something } } -
次のように、既存のオブジェクトを使用して非静的メソッドへの参照を渡します。
objectName::instanceMethodNamepublic class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Dota"); strings.add("GTA5"); strings.add("Halo"); Main instance = new Main(); strings.forEach(instance::nonStaticMethod); } private void nonStaticMethod(String s) { // Do something } } -
次のように、非静的メソッドを実装するクラスを使用して、非静的メソッドへの参照を渡します。
ClassName::methodNamepublic class Main { public static void main(String[] args) { List<User> users = new LinkedList<>(); users.add (new User("John")); users.add(new User("Paul")); users.add(new User("George")); users.forEach(User::print); } private static class User { private String name; private User(String name) { this.name = name; } private void print() { System.out.println(name); } } } -
次のようにコンストラクターに参照を渡します。
ClassName::newメソッド参照は、コールバックとして完全に機能するメソッドがすでにある場合に非常に便利です。この場合、メソッドのコードを含むラムダ式を作成したり、単にメソッドを呼び出すラムダ式を作成したりする代わりに、メソッドへの参照を渡すだけです。以上です。
匿名クラスとラムダ式の興味深い違い
匿名クラスでは、thisキーワードは匿名クラスのオブジェクトを指します。しかし、これをラムダ内で使用すると、それを含むクラスのオブジェクトにアクセスできるようになります。実際にラムダ式を書いたもの。これは、ラムダ式が、ラムダ式が記述されているクラスのプライベート メソッドにコンパイルされるために発生します。この「機能」には副作用があり、関数型プログラミングの原則に矛盾するため、使用はお勧めしません。とはいえ、このアプローチは OOP と完全に一致しています。;)
情報はどこで入手しましたか?他に何を読む必要がありますか?
- Oracle の公式 Web サイトにあるチュートリアル。事例も含めて詳しい情報が満載です。
- 同じ Oracle チュートリアルのメソッド参照に関する章。
- 本当に興味があるなら、Wikipediaに夢中になってみてください。
GO TO FULL VERSION