Pandangan saya tentang ekspresi lambda di java cukup kusut:
Yang saya pikir seperti ini: Ekspresi Lambda mengurangi pengalaman membaca program Java. Program Java tidak pernah luar biasa dalam ekspresif. Sebaliknya, salah satu faktor yang membuat Java populer adalah keamanan dan konservatisme-bahkan pemula dapat menulis kode yang kuat dan mudah diraih selama mereka memperhatikannya. Ekspresi Lambda memiliki persyaratan yang relatif lebih tinggi untuk pengembang, sehingga mereka juga meningkatkan beberapa kesulitan pemeliharaan.
Hal lain yang saya pikirkan adalah: sebagai kode kode, perlu untuk mempelajari dan menerima fitur baru bahasa. Jika Anda melepaskan kekuatan ekspresifnya hanya karena pengalaman membaca yang buruk, maka beberapa orang merasa sulit untuk memahami bahkan ekspresi trinokular. Bahasa juga berkembang, dan mereka yang tidak bisa mengikuti akan tertinggal secara sukarela.
Saya tidak ingin tertinggal. Namun, jika saya harus membuat pilihan, keputusan saya masih relatif konservatif: tidak perlu menggunakan lambda dalam bahasa Java - itu membuat banyak orang di lingkaran Java saat ini tidak terbiasa dengan itu, dan akan menyebabkan peningkatan biaya tenaga kerja. Jika Anda sangat menyukainya, Anda dapat mempertimbangkan untuk menggunakan Scala.
Ngomong -ngomong, saya masih mulai mencoba menguasai Lambda, setelah semua, beberapa kode yang dikelola di tempat kerja menggunakan Lambda (percayalah, saya secara bertahap akan menghapusnya). Tutorial untuk dipelajari adalah tutorial terkait di situs web resmi Oracle Java.
-
Misalkan aplikasi jejaring sosial saat ini sedang dibuat. Salah satu fitur adalah bahwa administrator dapat melakukan tindakan tertentu pada anggota yang memenuhi kriteria yang ditentukan, seperti mengirim pesan. Tabel berikut menjelaskan kasus penggunaan ini secara rinci:
| Bidang | menggambarkan |
| nama | Tindakan untuk dilakukan |
| Peserta kunci | administrator |
| Prasyarat | Administrator Login ke sistem |
| Pasca-kondisi | Hanya melakukan tindakan untuk anggota yang memenuhi kriteria yang ditentukan |
| Skenario Sukses Utama | 1. Administrator menetapkan standar penyaringan untuk anggota target untuk melakukan operasi; 2. Administrator memilih tindakan yang akan dilakukan; 3. Administrator mengklik tombol Kirim; 4. Sistem menemukan anggota yang memenuhi kriteria yang ditentukan; 5. Sistem melakukan operasi yang telah dipilih sebelumnya pada anggota yang memenuhi kriteria yang ditentukan. |
| Diperpanjang | Sebelum memilih operasi eksekusi atau sebelum mengklik tombol Kirim, administrator dapat memilih apakah akan mempratinjau informasi anggota yang memenuhi kriteria penyaringan. |
| Frekuensi kejadian | Itu terjadi berkali -kali dalam sehari. |
Gunakan kelas orang berikut untuk mewakili informasi anggota di jejaring sosial:
orang kelas publik {public enum sex {pria, wanita} nama string; Ulang tahun lokal; Jenis kelamin seks; String emailAddress; public int getage () {// ...} public void printperson () {// ...}}Asumsikan bahwa semua anggota disimpan dalam daftar <sone> contoh.
Di bagian ini, kami mulai dengan metode yang sangat sederhana, kemudian mencoba mengimplementasikannya menggunakan kelas lokal dan kelas anonim, dan pada akhirnya kami akan secara bertahap mengalami kekuatan dan efisiensi ekspresi lambda. Kode lengkap dapat ditemukan di sini.
Solusi 1: Buat metode untuk menemukan anggota yang memenuhi kriteria yang ditentukan satu per satu
Ini adalah solusi paling sederhana dan paling kasar untuk mengimplementasikan kasus -kasus yang disebutkan di atas: adalah untuk membuat beberapa metode dan setiap metode memverifikasi kriteria (seperti usia atau jenis kelamin). Kode berikut memverifikasi bahwa usia lebih tua dari nilai yang ditentukan:
public static void printpersonsolderthan (daftar <son> roster, int usia) {for (orang p: roster) {if (p.getage ()> = usia) {p.printperson (); }}}Ini adalah solusi yang sangat rapuh, dan sangat mungkin bahwa aplikasi tidak akan berjalan karena sedikit pembaruan. Jika kami menambahkan variabel anggota baru ke kelas orang atau mengubah algoritma untuk mengukur usia dalam standar, kita perlu menulis ulang banyak kode untuk beradaptasi dengan perubahan ini. Selain itu, pembatasan di sini terlalu kaku. Misalnya, apa yang harus kita lakukan jika kita ingin mencetak anggota yang lebih muda dari nilai yang ditentukan? Tambahkan metode baru printpersonygerthan? Ini jelas merupakan metode yang bodoh.
Solusi 2: Buat metode yang lebih umum
Metode berikut lebih mudah beradaptasi daripada printpersonsolderthan; Metode ini mencetak informasi anggota dalam kelompok usia yang ditentukan:
public static void printpersonsWithInagerange (daftar <fone> roster, int low, int high) {for (person p: roster) {if (low <= p.getage () && p.getage () <tinggi) {p.printperson (); }}}Sekarang ada ide baru: apa yang harus kita lakukan jika kita ingin mencetak informasi anggota dari jenis kelamin yang ditentukan, atau yang memenuhi jenis kelamin yang ditentukan dan berada dalam kelompok usia yang ditentukan? Bagaimana jika kita menyesuaikan kelas orang dan menambahkan properti seperti persahabatan dan lokasi geografis. Meskipun metode penulisan seperti ini lebih universal daripada printpersonsyygerhan, menulis metode untuk setiap kueri yang mungkin juga dapat menyebabkan kerapuhan dalam kode. Lebih baik memasukkan kode cek standar ke dalam kelas baru.
Solusi 3: Menerapkan inspeksi standar di kelas lokal
Metode berikut mencetak informasi anggota yang memenuhi kriteria pencarian:
public static void printpersons (daftar <son> Roster, checkperson tester) {for (orang p: roster) {if (tester.test (p)) {p.printperson (); }}}Penguji objek Checkperso digunakan dalam program untuk memverifikasi setiap instance dalam daftar parameter daftar. Jika tester.test () mengembalikan true, metode printperson () akan dieksekusi. Untuk mengatur kriteria pencarian, antarmuka Checkperson perlu diimplementasikan.
Kelas berikut mengimplementasikan pemeriksa dan memberikan implementasi spesifik dari metode pengujian. Metode pengujian dalam kelas ini menyaring informasi tentang keanggotaan yang memenuhi persyaratan untuk dinas militer di Amerika Serikat: yaitu, jenis kelamin laki -laki dan usia antara 18 dan 25 tahun.
kelas checkPersoneliGibleForSelectiveservice mengimplementasikan checkperson {tes public boolean (orang p) {return p.gender == person.sex.male && p.getage ()> = 18 && p.getage () <= 25; }}Untuk menggunakan kelas ini, Anda perlu membuat instance dan memicu metode printpersons:
printpersons (daftar, checksersoneligibleForselectiveservice () baru;
Kode sekarang terlihat kurang rapuh - kita tidak perlu menulis ulang kode karena perubahan dalam struktur kelas orang. Namun, masih ada kode tambahan di sini: antarmuka yang baru didefinisikan yang mendefinisikan kelas internal untuk setiap standar pencarian dalam aplikasi.
Karena CheckPersoneligibleForselectiveservice mengimplementasikan antarmuka, kelas anonim dapat digunakan tanpa mendefinisikan kelas dalam untuk setiap standar.
Solusi 4: Gunakan kelas anonim untuk mengimplementasikan inspeksi standar
Salah satu parameter dalam metode printpersons yang disebut di bawah ini adalah kelas anonim. Fungsi kelas anonim ini sama dengan yang dari CheckPerersonligibleForselectiveservice kelas dalam Skema 3: mereka semua adalah anggota yang difilter dengan jenis kelamin pria dan usia antara 18 dan 25 tahun.
printpersons (daftar, checkPerson baru () {tes public boolean (orang p) {return p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25;}});Skema ini mengurangi jumlah pengkodean, karena tidak ada lagi kebutuhan untuk membuat kelas baru untuk setiap skema pencarian yang akan dieksekusi. Namun, masih agak tidak nyaman untuk melakukan ini: meskipun antarmuka Checkperson hanya memiliki satu metode, kelas anonim yang diimplementasikan masih sedikit bertele -tele dan tebal. Saat ini, Anda dapat menggunakan ekspresi Lambda untuk menggantikan kelas anonim. Berikut ini akan menjelaskan cara menggunakan ekspresi lambda untuk menggantikan kelas anonim.
Solusi 5: Gunakan ekspresi lambda untuk mengimplementasikan pemeriksaan standar
Antarmuka Checkperson adalah antarmuka fungsional. Antarmuka fungsional yang disebut mengacu pada antarmuka apa pun yang hanya berisi satu metode abstrak. (Antarmuka fungsional juga dapat memiliki beberapa metode default atau metode statis). Karena hanya ada satu metode abstrak dalam antarmuka fungsional, nama metode metode ini dapat dihilangkan saat mengimplementasikan metode antarmuka fungsional ini. Untuk mengimplementasikan ide ini, Anda dapat mengganti ekspresi kelas anonim dengan ekspresi lambda. Dalam metode printpersons yang ditulis ulang di bawah ini, kode yang relevan disorot:
printpersons (roster, (orang p) -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25);
Di sini Anda juga dapat menggunakan antarmuka fungsi standar untuk mengganti antarmuka CheckPerson, sehingga lebih jauh menyederhanakan kode.
Solusi 6: Gunakan antarmuka fungsional standar dalam ekspresi lambda
Mari kita lihat antarmuka Checkperson:
antarmuka checkperson {tes boolean (orang p); }Ini adalah antarmuka yang sangat sederhana. Karena hanya ada satu metode abstrak, itu juga antarmuka fungsional. Metode abstrak ini hanya menerima satu parameter dan mengembalikan nilai boolean. Antarmuka abstrak ini sangat sederhana sehingga kami akan mempertimbangkan apakah perlu untuk mendefinisikan antarmuka seperti itu dalam aplikasi. Pada saat ini, Anda dapat mempertimbangkan menggunakan antarmuka fungsional standar yang ditentukan oleh JDK, dan Anda dapat menemukan antarmuka ini di bawah paket Java.util.function.
Dalam contoh ini, kita dapat menggunakan antarmuka Predikat <T> untuk menggantikan Checkperson. Ada metode tes boolean (tt) dalam antarmuka ini:
Predikat antarmuka <T> {uji boolean (tt); }Antarmuka predikat <T> adalah antarmuka generik. Kelas generik (atau antarmuka generik) menentukan satu atau lebih parameter jenis menggunakan sepasang braket sudut (<>). Hanya ada satu parameter tipe di antarmuka ini. Saat Anda mendeklarasikan atau instantiate kelas generik menggunakan kelas konkret, Anda mendapatkan kelas parameter. Misalnya, predikat kelas parameter <son> seperti ini:
antarmuka predikat <sone> {tes boolean (orang t); }Di kelas parameter ini, ada metode yang konsisten dengan parameter dan nilai pengembalian dari metode Checkperson.Boolean Test (Person P). Oleh karena itu, Anda dapat menggunakan antarmuka Predikat <T> untuk mengganti antarmuka CheckPerson seperti yang ditunjukkan dalam metode berikut:
public static void printpersonsWithPredicate (daftar <fone> roster, predikat <sone> tester) {for (Person p: roster) {if (tester.test (p)) {p.printperson (); }}}Kemudian gunakan kode berikut untuk memfilter anggota dinas militer seperti dalam Rencana 3:
printpersonsWithPredicate (roster, p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25);
Pernahkah Anda memperhatikan bahwa saat menggunakan predikat <Fone> sebagai jenis parameter, tidak ada jenis parameter eksplisit yang ditentukan. Ini bukan satu -satunya tempat di mana ekspresi lambda diterapkan. Skema berikut akan memperkenalkan lebih banyak penggunaan ekspresi Lambda.
Solusi 7: Gunakan ekspresi lambda di seluruh aplikasi
Mari kita lihat metode printpersonswithPredicate dan pertimbangkan apakah Anda dapat menggunakan ekspresi lambda di sini:
public static void printpersonsWithPredicate (daftar <fone> roster, predikat <sone> tester) {for (Person p: roster) {if (tester.test (p)) {p.printperson (); }}}Dalam metode ini, setiap contoh orang dalam daftar diperiksa menggunakan predikat tester instance. Jika instance orang mematuhi kriteria cek yang ditentukan dalam tester, metode printperson dari instance orang akan dipicu.
Selain memicu metode printperson, contoh orang yang memenuhi standar tester juga dapat menjalankan metode lain. Anda dapat mempertimbangkan untuk menggunakan ekspresi Lambda untuk menentukan metode yang akan dieksekusi (saya pikir fitur ini bagus, yang memecahkan masalah bahwa metode dalam java tidak dapat dilewati sebagai objek). Sekarang Anda memerlukan ekspresi lambda yang mirip dengan metode printperson - ekspresi lambda yang hanya membutuhkan satu parameter dan mengembalikan batal. Ingat satu hal: untuk menggunakan ekspresi lambda, Anda perlu menerapkan antarmuka fungsional terlebih dahulu. Dalam contoh ini, antarmuka fungsional diperlukan, yang hanya berisi satu metode abstrak. Metode abstrak ini memiliki parameter tipe orang dan kembali ke void. Anda dapat melihat konsumen antarmuka fungsional standar <T> yang disediakan oleh JDK, yang memiliki metode abstrak batal menerima (T T) hanya memenuhi persyaratan ini. Dalam kode berikut, gunakan instance konsumen <T> untuk memanggil metode penerimaan alih -alih p.printperson ():
Public static void ProcessPersons (Daftar <son> Roster, predikat <son> tester, Consumer <Fone> block) {for (Person P: Roster) {if (tester.test (p)) {block.accept (p); }}}Sejalan dengan itu, Anda dapat menggunakan kode berikut untuk memfilter anggota Zaman Layanan Militer:
ProcessPersons (Roster, p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.printperson ());
Jika kami ingin melakukan hal -hal yang tidak hanya mencetak informasi anggota, tetapi lebih banyak hal, seperti memverifikasi keanggotaan, mendapatkan informasi kontak anggota, dll. Pada titik ini, kami memerlukan antarmuka fungsional dengan metode nilai pengembalian. Fungsi Antarmuka Fungsional Standar JDK <T, R> memiliki metode seperti ini R Terapkan (T T). Metode berikut mendapatkan data dari parameter mapper dan melakukan perilaku yang ditentukan oleh blok parameter pada data ini:
Public static void ProcessPersonsWithFunction (Daftar <FERVER> Roster, Predikat <FONDE> TESTER, FUNGSI <Person, String> Mapper, Consumer <String> blok) {for (Person P: Roster) {if (tester.test (p)) {string data = mapper.Agply (p); block.accept (data); }}}Kode berikut memperoleh informasi email dari semua anggota Zaman Layanan Militer dalam daftar dan mencetaknya:
ProcessPersonsWithFunction (Roster, p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.geteMailAddress (), email -> System.out.println (email)));
Solusi 8: Gunakan generik lebih sering
Mari kita tinjau Metode ProcessPersonWithFunction. Berikut ini adalah versi generik dari metode ini. Metode baru membutuhkan lebih banyak toleransi dalam jenis parameter:
Public Static <x, y> void ProcessElements (Sumber <x> yang dapat Iterable, predikat <x> tester, fungsi <x, y> mapper, blok konsumen <Y) {for (x p: source) {if (tester.test (p)) {y data = mapper.Apply (p); block.accept (data); }}}Untuk mencetak informasi anggota untuk dinas militer pada usia yang tepat, Anda dapat menghubungi metode proses seperti berikut:
ProcessElements (Roster, p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.geteMailAddress (), email -> system.out.println (email));
Selama proses panggilan metode, perilaku berikut dilakukan:
Dapatkan informasi objek dari koleksi, dalam contoh ini, dapatkan informasi objek orang dari daftar contoh koleksi.
Filter objek yang dapat cocok dengan predikat instance tester. Dalam contoh ini, objek predikat adalah ekspresi lambda yang menentukan kondisi untuk menyaring dinas militer pada usia yang tepat.
Objek yang difilter diserahkan ke mapper objek fungsi untuk diproses, dan mapper akan cocok dengan nilai dengan objek ini. Dalam contoh ini, Function Object Mapper adalah ekspresi Lambda yang mengembalikan alamat email masing -masing anggota.
Menentukan perilaku oleh blok objek konsumen untuk nilai yang cocok dengan mapper. Dalam contoh ini, objek konsumen adalah ekspresi lambda, yang merupakan fungsi mencetak string, yang merupakan alamat email anggota yang dikembalikan oleh function instance mapper.
Solusi 9: Gunakan operasi agregasi menggunakan ekspresi lambda sebagai parameter
Kode berikut menggunakan operasi agregasi untuk mencetak alamat email anggota usia militer dalam koleksi daftar:
roster.stream () .filter (p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25) .map (p -> p.getemailAddress ()) .foreach (email -> System.out.println (email));
Menganalisis proses eksekusi kode di atas dan mengatur tabel berikut:
Perilaku | Operasi Agregasi |
Dapatkan objek | Stream <e> stream () |
Filter objek yang sesuai dengan kriteria yang ditentukan dari instance predikat | Stream <T> filter (predikat <? Super t> prediksi) |
Dapatkan nilai pencocokan objek melalui instance fungsi | <r> Stream <r> Peta (Fungsi <? Super T,? Memperluas R> mapper) |
Jalankan perilaku yang ditentukan oleh instance konsumen | void foreach (konsumen <? super t> aksi) |
Operasi filter, peta, dan foreach dalam tabel adalah semua operasi agregat. Elemen -elemen yang diproses oleh operasi agregasi berasal dari aliran, tidak langsung dari koleksi (yaitu, karena metode pertama yang disebut dalam program contoh ini adalah stream ()). Aliran adalah urutan data. Tidak seperti koleksi, stream tidak menyimpan data dengan struktur tertentu. Sebaliknya, Stream mendapat data dari sumber tertentu, seperti dari koleksi, melalui pipa. Pipeline adalah urutan operasi aliran, dalam contoh ini filter-map-foreach. Selain itu, operasi agregasi biasanya menggunakan ekspresi lambda sebagai parameter, yang juga memberi kita banyak ruang khusus.