Untuk siapa artikel ini?
- Ia untuk orang yang membaca bahagian pertama artikel ini;
- Ia adalah untuk orang yang berpendapat mereka sudah mengetahui Java Core dengan baik, tetapi tidak tahu tentang ungkapan lambda di Jawa. Atau mungkin mereka pernah mendengar sesuatu tentang ungkapan lambda, tetapi butirannya kurang.
- Ia adalah untuk orang yang mempunyai pemahaman tertentu tentang ungkapan lambda, tetapi masih takut dengannya dan tidak biasa menggunakannya.
Akses kepada pembolehubah luaran
Adakah kod ini disusun dengan kelas tanpa nama?
int counter = 0;
Runnable r = new Runnable() {
@Override
public void run() {
counter++;
}
};
Tidak. counter
Pembolehubah mestilah final
. Atau jika tidak final
, maka sekurang-kurangnya ia tidak boleh mengubah nilainya. Prinsip yang sama berlaku dalam ungkapan lambda. Mereka boleh mengakses semua pembolehubah yang mereka boleh "lihat" dari tempat mereka diisytiharkan. Tetapi lambda tidak boleh mengubahnya (berikan nilai baharu kepada mereka). Walau bagaimanapun, terdapat cara untuk memintas sekatan ini dalam kelas tanpa nama. Hanya buat pembolehubah rujukan dan tukar keadaan dalaman objek. Dengan berbuat demikian, pembolehubah itu sendiri tidak berubah (menunjuk ke objek yang sama) dan boleh ditanda dengan selamat sebagai final
.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
}
};
Di sini pembolehubah kami counter
adalah rujukan kepada AtomicInteger
objek. Dan incrementAndGet()
kaedah digunakan untuk menukar keadaan objek ini. Nilai pembolehubah itu sendiri tidak berubah semasa program sedang berjalan. Ia sentiasa menunjuk ke objek yang sama, yang membolehkan kami mengisytiharkan pembolehubah dengan kata kunci akhir. Berikut adalah contoh yang sama, tetapi dengan ungkapan lambda:
int counter = 0;
Runnable r = () -> counter++;
Ini tidak akan dikompil atas sebab yang sama seperti versi dengan kelas tanpa nama: counter
tidak boleh berubah semasa program sedang berjalan. Tetapi semuanya baik-baik saja jika kita melakukannya seperti ini:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Ini juga terpakai pada kaedah panggilan. Dalam ungkapan lambda, anda bukan sahaja boleh mengakses semua pembolehubah "kelihatan", tetapi juga memanggil sebarang kaedah yang boleh diakses.
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!");
}
}
Walaupun staticMethod()
bersifat peribadi, ia boleh diakses di dalam main()
kaedah, jadi ia juga boleh dipanggil dari dalam lambda yang dibuat dalam main
kaedah tersebut.
Bilakah ungkapan lambda dilaksanakan?
Anda mungkin mendapati soalan berikut terlalu mudah, tetapi anda harus bertanya sama sahaja: bilakah kod di dalam ungkapan lambda akan dilaksanakan? Bilakah ia dicipta? Atau apabila disebut (yang belum diketahui)? Ini agak mudah untuk diperiksa.
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();
Output skrin:
Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Anda boleh melihat bahawa ungkapan lambda telah dilaksanakan pada penghujungnya, selepas benang dibuat dan hanya apabila pelaksanaan program mencapai kaedah run()
. Sudah tentu tidak apabila ia diisytiharkan. Dengan mengisytiharkan ungkapan lambda, kami hanya mencipta Runnable
objek dan menerangkan cara run()
kaedahnya bertindak. Kaedah itu sendiri dilaksanakan lebih lama kemudian.
Rujukan kaedah?
Rujukan kaedah tidak berkaitan secara langsung dengan lambda, tetapi saya rasa masuk akal untuk menyatakan beberapa perkataan tentangnya dalam artikel ini. Katakan kita mempunyai ungkapan lambda yang tidak melakukan sesuatu yang istimewa, tetapi hanya memanggil kaedah.
x -> System.out.println(x)
Ia menerima beberapa x
dan hanya panggilan System.out.println()
, melalui x
. Dalam kes ini, kita boleh menggantikannya dengan rujukan kepada kaedah yang dikehendaki. seperti ini:
System.out::println
Betul — tiada kurungan pada penghujungnya! Berikut ialah contoh yang lebih lengkap:
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(x -> System.out.println(x));
Dalam baris terakhir, kami menggunakan forEach()
kaedah, yang mengambil objek yang melaksanakan Consumer
antara muka. Sekali lagi, ini adalah antara muka berfungsi, hanya mempunyai satu void accept(T t)
kaedah. Sehubungan itu, kami menulis ungkapan lambda yang mempunyai satu parameter (kerana ia ditaip dalam antara muka itu sendiri, kami tidak menyatakan jenis parameter; kami hanya menunjukkan bahawa kami akan memanggilnya x
). Dalam badan ungkapan lambda, kami menulis kod yang akan dilaksanakan apabila kaedah accept()
dipanggil. Di sini kita hanya memaparkan apa yang berakhir dalam x
pembolehubah. Kaedah yang sama ini forEach()
berulang melalui semua elemen dalam koleksi dan memanggil accept()
kaedah pada pelaksanaanConsumer
antara muka (lambda kami), menghantar setiap item dalam koleksi. Seperti yang saya katakan, kita boleh menggantikan ungkapan lambda sedemikian (yang hanya mengkelaskan kaedah yang berbeza) dengan merujuk kepada kaedah yang dikehendaki. Kemudian kod kami akan kelihatan seperti ini:
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(System.out::println);
Perkara utama ialah parameter println()
dan accept()
kaedah sepadan. Oleh kerana kaedah itu println()
boleh menerima apa-apa sahaja (ia terlebih beban untuk semua jenis primitif dan semua objek), bukannya ungkapan lambda, kita hanya boleh menghantar rujukan kepada kaedah println()
kepada forEach()
. Kemudian forEach()
akan mengambil setiap elemen dalam koleksi dan menghantarnya terus ke println()
kaedah. Bagi sesiapa yang menghadapi perkara ini buat kali pertama, sila ambil perhatian bahawa kami tidak memanggil System.out.println()
(dengan titik antara perkataan dan dengan kurungan di hujungnya). Sebaliknya, kami memberikan rujukan kepada kaedah ini. Jika kita menulis ini
strings.forEach(System.out.println());
kami akan mempunyai ralat penyusunan. Sebelum panggilan ke forEach()
, Java melihat bahawa System.out.println()
sedang dipanggil, jadi ia memahami bahawa nilai pulangan adalah void
dan akan cuba dihantar void
ke forEach()
, yang sebaliknya mengharapkan Consumer
objek.
Sintaks untuk rujukan kaedah
Ia agak mudah:-
Kami memberikan rujukan kepada kaedah statik seperti ini:
ClassName::staticMethodName
public 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 } }
-
Kami menghantar rujukan kepada kaedah bukan statik menggunakan objek sedia ada, seperti ini:
objectName::instanceMethodName
public 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 } }
-
Kami memberikan rujukan kepada kaedah bukan statik menggunakan kelas yang melaksanakannya seperti berikut:
ClassName::methodName
public 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); } } }
-
Kami menyampaikan rujukan kepada pembina seperti ini:
ClassName::new
Rujukan kaedah sangat mudah apabila anda sudah mempunyai kaedah yang akan berfungsi dengan sempurna sebagai panggilan balik. Dalam kes ini, daripada menulis ungkapan lambda yang mengandungi kod kaedah atau menulis ungkapan lambda yang hanya memanggil kaedah, kami hanya menyampaikan rujukan kepadanya. Dan itu sahaja.
Perbezaan menarik antara kelas tanpa nama dan ungkapan lambda
Dalam kelas tanpa nama,this
kata kunci menghala ke objek kelas tanpa nama. Tetapi jika kita menggunakan ini di dalam lambda, kita mendapat akses kepada objek kelas yang mengandungi. Yang mana kita sebenarnya menulis ungkapan lambda. Ini berlaku kerana ungkapan lambda dikompilasi ke dalam kaedah persendirian kelas yang ditulis. Saya tidak mengesyorkan menggunakan "ciri" ini kerana ia mempunyai kesan sampingan dan bercanggah dengan prinsip pengaturcaraan berfungsi. Walau bagaimanapun, pendekatan ini konsisten sepenuhnya dengan OOP. ;)
Di manakah saya mendapat maklumat saya dan apa lagi yang perlu anda baca?
- Tutorial di laman web rasmi Oracle. Banyak maklumat terperinci, termasuk contoh.
- Bab tentang rujukan kaedah dalam tutorial Oracle yang sama.
- Masuk ke Wikipedia jika anda benar-benar ingin tahu.
GO TO FULL VERSION