この記事は誰に向けたものですか?
- この記事の前半を読んだ方へ;
- Java Core についてはすでによく知っているが、Java のラムダ式についてはまったく知らない人向けです。あるいは、ラムダについてはすでに聞いたことがあるかもしれませんが、詳細は知りません。
- ラムダ式をある程度理解しているが、まだ使用するのが怖くて慣れていない人向けです。
外部変数へのアクセス
このコードは匿名クラスでコンパイルされますか?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("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
}
}
メソッドstaticMethod()
はプライベートですが、メソッド内で呼び出すことが「アクセス可能」であるmain()
ため、メソッド内で作成されたラムダ内から呼び出すこともできますmain
。
ラムダ式コードの実行の瞬間
この質問は単純すぎるように思えるかもしれませんが、ラムダ式内のコードはいつ実行されるのかということを問う価値はあります。創作の瞬間に?それとも(まだどこで呼び出されるのかは不明ですが)その瞬間に呼び出されるのでしょうか?確認するのはとても簡単です。System.out.println("Запуск программы");
// много всякого разного codeа
// ...
System.out.println("Перед объявлением лямбды");
Runnable runnable = () -> System.out.println("Я - лямбда!");
System.out.println("После объявления лямбды");
// много всякого другого codeа
// ...
System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
ディスプレイ上の出力:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
ラムダ式コードは、スレッドが作成された後、プログラム実行プロセスがメソッドの実際の実行に達したときのみ、最後に実行されたことがわかりますrun()
。そして、発表時にはまったくそうではありませんでした。ラムダ式を宣言することで、 型のオブジェクトを作成しRunnable
、そのメソッドの動作を記述しただけですrun()
。この方法自体はずっと後になってから導入されました。
メソッドのリファレンス?
ラムダ自体には直接関係しませんが、この記事でラムダについて少し述べておくのは論理的だと思います。特別なことは何もせず、メソッドを呼び出すだけのラムダ式があるとします。x -> System.out.println(x)
彼らは彼に何かを手渡しましたх
、そしてそれは単に彼を呼んSystem.out.println()
でそこに通しましたх
。この場合、必要なメソッドへのリンクに置き換えることができます。このような:
System.out::println
はい、最後に括弧は必要ありません。より完全な例:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");
strings.forEach(x -> System.out.println(x));
forEach()
最後の行では、インターフェイス オブジェクトを受け入れる メソッドを使用しますConsumer
。これもメソッドが 1 つだけある機能的なインターフェイスですvoid accept(T t)
。したがって、1 つのパラメーターを取るラムダ式を作成します (インターフェイス自体に型指定されているため、パラメーターの型は示されませんが、それが呼び出されることが示されています。ラムダ式х)
の本体には、次のコードを記述します)これは、メソッドが呼び出されたときに実行されますaccept()
。ここでは、変数の内容を画面に表示するだけですх
。メソッド自体はコレクションのすべての要素を調べ、それに渡されたインターフェイス オブジェクトのメソッド (ラムダ) をforEach()
呼び出します。ここで、コレクションの各要素を渡します。すでに述べたように、これはラムダ式 (単に別のメソッドを呼び出すだけ) で、必要なメソッドへの参照に置き換えることができます。その場合、コードは次のようになります。 Consumer
accept()
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");
strings.forEach(System.out::println);
主なことは、メソッドと のパラメータが受け入れられること(println()
ですaccept())
。このメソッドはprintln()
何でも受け入れることができるので (すべてのプリミティブとオブジェクトに対してオーバーロードされています)、ラムダ式の代わりに、forEach()
メソッドへの参照だけを渡すことができますprintln()
。その後、forEach()
コレクションの各要素を取得して、それを直接 に渡します。println()
初めてこれに遭遇する人は、メソッドを呼び出すのではなく (単語の間にドットを使用し、最後に括弧を付けて)、このメソッド自体への参照を渡していることに注意してくださいSystem.out.println()
。
strings.forEach(System.out.println());
コンパイルエラーが発生します。呼び出しの前にforEach()
、Java はそれが呼び出されていることを確認しSystem.out.println()
、それが返されていることを理解しvoid
、そこで待機している型のオブジェクトvoid
にこれを渡そうとします。 forEach()
Consumer
メソッド参照を使用するための構文
それは非常に簡単です:-
静的メソッドへの参照の受け渡し
NameКласса:: NameСтатическогоМетода?
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Mother"); strings.add("soap"); strings.add("frame"); strings.forEach(Main::staticMethod); } private static void staticMethod(String s) { // do something } }
-
既存のオブジェクトを使用して非静的メソッドに参照を渡す
NameПеременнойСОбъектом:: method name
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Mother"); strings.add("soap"); strings.add("frame"); Main instance = new Main(); strings.forEach(instance::nonStaticMethod); } private void nonStaticMethod(String s) { // do something } }
-
非静的メソッドが実装されているクラスを使用して、非静的メソッドへの参照を渡します。
NameКласса:: method name
public class Main { public static void main(String[] args) { List<User> users = new LinkedList<>(); users.add(new User("Vasya")); users.add(new User("Коля")); users.add(new User("Петя")); 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); } } }
-
コンストラクターにリンクを渡す
NameКласса::new
メソッド リンクの使用は、完全に満足できる既製のメソッドがあり、それをコールバックとして使用したい場合に非常に便利です。この場合、そのメソッドのコードを使用してラムダ式を作成したり、単にこのメソッドを呼び出すラムダ式を作成したりする代わりに、単にそのメソッドへの参照を渡します。それだけです。
匿名クラスとラムダ式の興味深い違い
匿名クラスでは、キーワードはthis
その匿名クラスのオブジェクトを指します。これをラムダ内で使用するとthis
、フレーミング クラスのオブジェクトにアクセスできるようになります。この式を実際に書いた場所。これは、ラムダ式がコンパイルされると、それが記述されたクラスのプライベート メソッドになるために発生します。この「機能」には関数型プログラミングの原則に反する副作用があるため、使用はお勧めしません。しかし、このアプローチは OOP と非常に一致しています。;)
情報をどこから入手したか、他に何を読むべきか
- Oracle 公式 Web サイトのチュートリアル。多くの詳細が例とともに記載されていますが、英語です。
- 同じ「Oracle」チュートリアルですが、この章はメソッド参照に関するものです。
- 関数型プログラミングに関するHabré の記事(別記事の翻訳)。ラムダは関数型プログラミング全般に関するものであるため、ラムダに関する複数の書籍はほとんどありません。
- Wikipediaに夢中になりたい人向け。
- そしてもちろん、Google でたくさんのことを見つけました:)
GO TO FULL VERSION