Pengantar Lambda
Lambda Expressions adalah fitur baru yang penting di Java SE 8. Ekspresi Lambda memungkinkan Anda untuk mengganti antarmuka fungsional berdasarkan ekspresi. Ekspresi lambda sama seperti metode, yang menyediakan daftar parameter normal dan tubuh (tubuh, yang dapat berupa ekspresi atau blok kode) yang menggunakan parameter ini.
Lambda Expressions juga meningkatkan perpustakaan koleksi. Java SE 8 menambahkan 2 paket yang mengoperasikan operasi batch pada data pengumpulan: java.util.function Paket dan java.util.stream Paket. Aliran seperti iterator, tetapi dengan banyak fitur tambahan terpasang. Secara umum, ekspresi dan aliran Lambda adalah perubahan terbesar karena bahasa Java menambahkan obat generik dan anotasi.
Ekspresi Lambda pada dasarnya adalah metode anonim, dan lapisan yang mendasarinya diimplementasikan melalui arahan invokedynamic untuk menghasilkan kelas anonim. Ini memberikan sintaksis yang lebih sederhana dan metode penulisan, memungkinkan Anda untuk mengganti antarmuka fungsional dengan ekspresi. Di mata beberapa orang, Lambda dapat membuat kode Anda lebih ringkas dan tidak menggunakannya sama sekali - pandangan ini tentu baik -baik saja, tetapi yang penting adalah bahwa Lambda membawa penutupan ke Jawa. Berkat dukungan LAMDBA untuk koleksi, Lambda telah sangat meningkatkan kinerja saat melintasi koleksi dalam kondisi prosesor multi-core. Selain itu, kami dapat memproses koleksi dalam bentuk aliran data - yang sangat menarik.
Sintaks Lambda
Sintaks Lambda sangat sederhana, mirip dengan struktur berikut:
(parameter) -> ekspresi
atau
(parameter) -> {pernyataan; }Ekspresi Lambda terdiri dari tiga bagian:
1. Paramat: Daftar parameter formal dalam metode yang sama, parameter di sini adalah parameter dalam antarmuka fungsional. Jenis parameter di sini dapat dinyatakan secara eksplisit atau tidak dinyatakan tetapi secara implisit disimpulkan oleh JVM. Selain itu, ketika hanya ada satu jenis inferensi, tanda kurung dapat dihilangkan.
2. ->: dapat dipahami sebagai "digunakan"
3. Badan Metode: Ini bisa berupa ekspresi atau blok kode, itu adalah implementasi metode dalam antarmuka fungsional. Blok kode dapat mengembalikan nilai atau pembalikan apa pun. Blok kode di sini setara dengan badan metode metode. Jika itu adalah ekspresi, Anda juga dapat mengembalikan nilai atau tidak mengembalikan apa pun.
Mari kita gunakan contoh -contoh berikut untuk menggambarkan:
//Example 1: No need to accept parameters, directly return 10()->10//Example 2: Accept two parameters of int type and return the sum of these two parameters (int x,int y)->x+y;//Example 2: Accept two parameters of x and y, the type of this parameter is inferred by the JVM based on the context, and returns the sum of the two parameters (x,y)->x+y;//Example 3: Accept a string and print the string to control, without reverse the result (String name)->System.out.println(name);//Example 4: Accept an inferred type parameter name and print the string to console name->System.out.println(name);//Example 5: Accept two String type parameters and output them separately, without reverse the result (String name,String name,String sex)-> {System.out.println (name); System.out.println (sex)} // Contoh 6: Terima parameter x dan kembalikan dua kali parameter x-> 2*xTempat menggunakan lambda
Dalam [antarmuka fungsional] [1] kita tahu bahwa tipe target ekspresi lambda adalah antarmuka fungsional - setiap lambda dapat mencocokkan tipe yang diberikan melalui antarmuka fungsional tertentu. Oleh karena itu, ekspresi Lambda dapat diterapkan di mana saja yang cocok dengan tipe targetnya. Ekspresi lambda harus memiliki tipe parameter yang sama dengan deskripsi fungsi abstrak dari antarmuka fungsional, tipe pengembaliannya juga harus kompatibel dengan jenis pengembalian fungsi abstrak, dan pengecualian yang dapat dilemparkannya terbatas pada rentang deskripsi fungsi.
Selanjutnya, mari kita lihat contoh antarmuka fungsional khusus:
@FunctionalInterface Interface Converter <f, t> {t convert (f from);}Pertama, gunakan antarmuka dengan cara tradisional:
Converter <string, integer> converter = converter baru <string, integer> () {@Override public integer convert (string from) {return integer.valueof (from); }}; Integer hasil = converter.convert ("200"); System.out.println (hasil);Jelas tidak ada masalah dengan ini, jadi hal berikutnya adalah saat ketika Lambda datang di lapangan, menggunakan Lambda untuk mengimplementasikan antarmuka konverter:
Converter <string, integer> converter = (param) -> integer.valueof (param); Integer hasil = converter.convert ("101"); System.out.println (hasil);Melalui contoh di atas, saya pikir Anda memiliki pemahaman sederhana tentang penggunaan lambda. Di bawah ini, kami menggunakan runnable yang umum digunakan untuk menunjukkan:
Di masa lalu kita mungkin telah menulis kode ini:
utas baru (runnable baru () {@Override public void run () {System.out.println ("Hello lambda");}}). start ();Dalam beberapa kasus, sejumlah besar kelas anonim dapat membuat kode tampak berantakan. Sekarang Anda dapat menggunakan lambda untuk membuatnya sederhana:
utas baru (() -> System.out.println ("Hello Lambda")). Start ();Referensi metode
Referensi metode adalah cara yang disederhanakan untuk menulis ekspresi lambda. Metode yang dirujuk sebenarnya adalah implementasi dari badan metode ekspresi Lambda, dan struktur sintaksinya adalah:
Objectref :: methodname
Sisi kiri dapat berupa nama kelas atau nama instance, bagian tengah adalah simbol referensi metode "::", dan sisi kanan adalah nama metode yang sesuai.
Referensi metode dibagi menjadi tiga kategori:
1. Referensi metode statis
Dalam beberapa kasus, kita mungkin menulis kode seperti ini:
Public Class ReferenceTest {public static void main (string [] args) {converter <string, integer> converter = converter baru <string, integer> () {@Override public integer convert (string from) {referenceTest.string2int (from); }}; converter.convert ("120"); } @FunctionalInterface Interface Converter <f, t> {t convert (f from); } static int string2int (string from) {return integer.valueof (from); }}Pada saat ini, jika Anda menggunakan referensi statis, kode akan lebih ringkas:
Converter <string, integer> converter = Referencetest :: string2int; converter.convert ("120");2. Referensi Metode Contoh
Kami mungkin juga menulis kode seperti ini:
ReferenceTest kelas publik {public static void main (string [] args) {converter <string, integer> converter = converter baru <string, integer> () {@Override public integer convert (string from) {return new helper (). string2int (from); }}; converter.convert ("120"); } @FunctionalInterface Interface Converter <f, t> {t convert (f from); } helper kelas statis {public int string2int (string from) {return integer.valueof (from); }}}Juga, menggunakan contoh metode untuk referensi akan tampak lebih ringkas:
Helper helper = helper baru (); Converter <string, integer> converter = helper :: string2int; converter.convert ("120");3. Referensi Metode Konstruktor
Sekarang mari kita tunjukkan referensi kepada konstruktor. Pertama kami mendefinisikan hewan kelas induk:
class hewan {private string name; usia int pribadi; hewan publik (nama string, int usia) {this.name = name; this.age = usia; } public void behavior () {}} Selanjutnya, kami mendefinisikan dua subclass hewan: Dog、Bird
Bird kelas publik memperluas hewan {public bird (name string, int era) {super (nama, usia); } @Override public void behavior () {System.out.println ("Fly"); }} class dog extends hewan {dog publik (nama string, int usia) {super (nama, usia); } @Override public void behavior () {System.out.println ("run"); }}Kemudian kami mendefinisikan antarmuka pabrik:
antarmuka pabrik <t extends hewan> {t create (nama string, int usia); }Selanjutnya, kami akan menggunakan metode tradisional untuk membuat objek kelas anjing dan burung:
Factory factory = new factory () {@Override Public Animal Create (nama string, int usia) {return new dog (nama, usia); }}; factory.create ("alias", 3); factory = new factory () {@Override Public Animal Create (nama string, int usia) {return new Bird (nama, usia); }}; factory.create ("smook", 2);Saya menulis lebih dari sepuluh kode hanya untuk membuat dua objek. Sekarang mari kita coba menggunakan referensi konstruktor:
Pabrik <En Animal> DogFactory = Dog :: New; Animal dog = dogfactory.create ("alias", 4); Pabrik <BIRD> BirdFactory = Bird :: New; Burung Bird = Birdfactory.create ("Smook", 3); Dengan cara ini kode tampak bersih dan rapi. Saat menggunakan Dog::new untuk menembus objek, pilih fungsi pembuatan yang sesuai dengan menandatangani fungsi Factory.create .
Domain dan pembatasan akses Lambda
Domain adalah ruang lingkup, dan parameter dalam daftar parameter dalam ekspresi lambda valid dalam ruang lingkup ekspresi lambda (domain). Dalam ekspresi lambda, variabel eksternal dapat diakses: variabel lokal, variabel kelas dan variabel statis, tetapi tingkat batasan operasi berbeda.
Mengakses variabel lokal
Variabel lokal di luar ekspresi lambda akan secara implisit disusun oleh JVM ke jenis akhir, sehingga mereka hanya dapat diakses tetapi tidak dimodifikasi.
Public Class ReferenceTest {public static void main (string [] args) {int n = 3; Hitung Hitung = Param -> {// n = 10; Kompilasi kesalahan pengembalian n + param; }; Hitung.kali (10); } @FunctionAlInterface Interface Calculate {int Calculate (nilai int); }}Akses variabel statis dan anggota
Di dalam ekspresi lambda, variabel statis dan anggota dapat dibaca dan dapat ditulis.
ReferenceTest kelas publik {public int count = 1; Public static int num = 2; public void test () {CHOCTULASI CALCOLATE = param -> {num = 10; // Ubah variabel statis count = 3; // Modifikasi variabel anggota pengembalian n + param; }; Hitung.kali (10); } public static void main (string [] args) {} @functionalinterface antarmuka Hitung {int calculate (value int); }}Lambda tidak dapat mengakses metode default antarmuka fungsi
JAVA8 Meningkatkan Antarmuka, termasuk metode default yang dapat menambahkan definisi kata kunci default ke antarmuka. Kita perlu mencatat di sini bahwa akses ke metode default tidak mendukung secara internal.
Praktik Lambda
Di bagian [antarmuka fungsional] [2], kami menyebutkan bahwa banyak antarmuka fungsional dibangun ke dalam paket java.util.function , dan sekarang kami akan menjelaskan antarmuka fungsional yang umum digunakan.
Antarmuka predikat
Masukkan parameter dan kembalikan nilai Boolean , yang berisi banyak metode default untuk penilaian logis:
@Test public void preded prededate () {predicate <string> predict = (s) -> s.length ()> 0; tes boolean = predict.test ("tes"); System.out.println ("Panjang string lebih besar dari 0:" + tes); test = predict.test (""); System.out.println ("Panjang string lebih besar dari 0:" + tes); Predikat <Peject> pre = objek :: nonnull; Objek ob = null; tes = pre.test (ob); System.out.println ("Objek tidak kosong:" + tes); ob = objek baru (); tes = pre.test (ob); System.out.println ("Objek tidak kosong:" + tes); }Antarmuka fungsi
Menerima parameter dan mengembalikan satu hasil. Metode default ( andThen ) dapat string beberapa fungsi bersama -sama untuk membentuk Funtion komposit (dengan input, output) hasil.
@Test public void functionTest () {function <string, integer> toInteger = integer :: valueof; // Hasil eksekusi tointeger digunakan sebagai input ke fungsi backtoString kedua <string, string> backtoString = tointeger.andthen (string :: valueof); Hasil String = BackToString.Apply ("1234"); System.out.println (hasil); Fungsi <integer, integer> add = (i) -> {System.out.println ("Input Frist:" + i); return i * 2; }; Fungsi <integer, integer> nol = add.andthen ((i) -> {system.out.println ("input kedua:" + i); return i * 0;}); Integer res = nol.Apply (8); System.out.println (res); }Antarmuka pemasok
Mengembalikan hasil dari jenis yang diberikan. Tidak seperti Function , Supplier tidak perlu menerima parameter (pemasok, dengan output tetapi tidak ada input)
@Test public void supplierTest () {pemasok <string> pemasok = () -> "nilai tipe khusus"; String S = Supplier.get (); System.out.println (s); }Antarmuka konsumen
Mewakili operasi yang perlu dilakukan pada parameter input tunggal. Tidak seperti Function , Consumer tidak mengembalikan nilai (konsumen, input, tidak ada output)
@Test public void consumerTest () {konsumen <integer> add5 = (p) -> {System.out.println ("nilai lama:" + p); p = p + 5; System.out.println ("Nilai Baru:" + P); }; add5.accept (10); } Penggunaan empat antarmuka di atas mewakili empat jenis dalam paket java.util.function . Setelah memahami keempat antarmuka fungsional ini, antarmuka lain akan mudah dimengerti. Sekarang mari kita buat ringkasan sederhana:
Predicate digunakan untuk penilaian logis, Function digunakan di tempat -tempat di mana ada input dan output, Supplier digunakan di tempat -tempat di mana tidak ada input dan output, dan Consumer digunakan di tempat -tempat di mana ada input dan tidak ada output. Anda dapat mengetahui skenario penggunaan berdasarkan arti namanya.
Sungai kecil
Lambda membawa penutupan untuk Java 8, yang sangat penting dalam operasi pengumpulan: Java 8 mendukung operasi fungsional pada aliran objek pengumpulan. Selain itu, API Stream juga diintegrasikan ke dalam API koleksi, yang memungkinkan operasi batch pada objek pengumpulan.
Mari kita kenal streaming.
Stream mewakili aliran data. Ia tidak memiliki struktur data dan tidak menyimpan elemen itu sendiri. Operasinya tidak akan mengubah aliran sumber, tetapi menghasilkan aliran baru. Sebagai antarmuka untuk data operasi, ia menyediakan penyaringan, penyortiran, pemetaan, dan regulasi. Metode -metode ini dibagi menjadi dua kategori sesuai dengan tipe pengembalian: metode apa pun yang mengembalikan tipe aliran disebut metode perantara (operasi perantara), dan sisanya adalah metode penyelesaian (operasi lengkap). Metode penyelesaian mengembalikan nilai dari beberapa jenis, sedangkan metode perantara mengembalikan aliran baru. Panggilan metode perantara biasanya dirantai, dan prosesnya akan membentuk pipa. Ketika metode terakhir dipanggil, itu akan menyebabkan nilainya segera dikonsumsi dari pipa. Di sini kita harus ingat: operasi aliran berjalan sebagai "tertunda" mungkin, yang sering kita sebut "operasi malas", yang akan membantu mengurangi penggunaan sumber daya dan meningkatkan kinerja. Untuk semua operasi menengah (kecuali diurutkan) mereka dijalankan dalam mode penundaan.
Stream tidak hanya memberikan kemampuan operasi data yang kuat, tetapi yang lebih penting, Stream mendukung serial dan paralelisme. Paralelisme memungkinkan aliran memiliki kinerja yang lebih baik pada prosesor multi-core.
Proses penggunaan aliran memiliki pola yang tetap:
1. Buat aliran
2. Melalui operasi menengah, "Ubah" aliran asli dan menghasilkan aliran baru
3. Gunakan operasi penyelesaian untuk menghasilkan hasil akhir
Yaitu
Buat -> Ubah -> Lengkap
Penciptaan aliran
Untuk koleksi, dapat dibuat dengan memanggil stream() atau parallelStream() . Selain itu, kedua metode ini juga diimplementasikan dalam antarmuka koleksi. Untuk array, mereka dapat dibuat dengan metode statis Stream of(T … values) . Selain itu, Array juga memberikan dukungan untuk aliran.
Selain membuat aliran berdasarkan koleksi atau array di atas, Anda juga dapat membuat aliran kosong melalui Steam.empty() , atau menggunakan Stream's generate() untuk membuat aliran tak terbatas.
Mari kita ambil aliran serial sebagai contoh untuk menggambarkan beberapa metode aliran antara dan penyelesaian yang umum digunakan. Pertama -tama buat koleksi daftar:
Daftar <String> Daftar = ArrayList baru <String> (); lists.add ("a1"); lists.add ("a2"); Daftar.Add ("B1"); Daftar.Add ("B2"); lists.add ("b3"); Daftar.Add ("O1");Metode Menengah
Menyaring
Dikombinasikan dengan antarmuka predikat, filter filter semua elemen dalam objek streaming. Operasi ini adalah operasi perantara, yang berarti Anda dapat melakukan operasi lain berdasarkan hasil yang dikembalikan oleh operasi.
public static void streamfiltertest () {lists.stream (). filter ((s -> s.startswith ("a"))). foreach (System.out :: println); // setara dengan operasi di atas predikat <string> predicate = (s) -> s.startswith ("a"); lists.stream (). filter (predikat) .foreach (System.out :: println); // Predikat penyaringan kontinu <string> predicate1 = (s -> s.endswith ("1")); lists.stream (). filter (predikat) .filter (predicate1) .foreach (System.out :: println); }Urutkan (disortir)
Dikombinasikan dengan antarmuka pembanding, operasi ini mengembalikan tampilan aliran yang diurutkan, dan urutan aliran asli tidak akan berubah. Aturan kolasi ditentukan melalui pembanding, dan standarnya adalah mengurutkannya dalam urutan alami.
public static void streamSortedTest () {System.out.println ("Default Comparator"); lists.stream (). disortir (). filter ((s -> s.startswith ("a"))). foreach (System.out :: println); System.out.println ("Custom Comparator"); lists.stream (). disortir ((p1, p2) -> p2.compareto (p1)). filter ((s -> s.startswith ("a"))). foreach (System.out :: println); }Peta (peta)
Dikombinasikan dengan antarmuka Function , operasi ini dapat memetakan setiap elemen dalam objek aliran ke elemen lain, mewujudkan konversi jenis elemen.
public static void streamMaptest () {lists.stream (). map (string :: touppercase) .sorted ((a, b) -> b.careto (a)). foreach (System.out :: println); System.out.println ("Aturan Pemetaan Kustom"); Function <string, string> function = (p) -> {return p + ".txt"; }; lists.stream (). Map (String :: Touppercase) .map (function) .sorted ((a, b) -> b.c.careto (a)). foreach (System.out :: println); }Di atas secara singkat memperkenalkan tiga operasi yang umum digunakan, yang sangat menyederhanakan pemrosesan koleksi. Selanjutnya, kami memperkenalkan beberapa cara untuk menyelesaikan:
Metode finishing
Setelah proses "transformasi", hasilnya perlu diperoleh, yaitu operasi selesai. Mari kita lihat operasi terkait di bawah ini:
Cocok
Digunakan untuk menentukan apakah suatu predicate cocok dengan objek aliran, dan akhirnya mengembalikan hasil tipe Boolean , misalnya:
public static void streamMatchTest () {// return true selama satu elemen dalam objek stream cocok dengan boolean anystartwitha = lists.stream (). anymatch ((s -> s.startswith ("a"))); System.out.println (Anystartwitha); // Kembalikan true ketika setiap elemen dalam objek stream cocok dengan boolean allstartwitha = lists.stream (). Allmatch ((s -> s.startswith ("a"))); System.out.println (Allstartwitha); }Mengumpulkan
Setelah transformasi, kami mengumpulkan unsur -unsur aliran yang ditransformasikan, seperti menyimpan elemen -elemen ini ke dalam koleksi. Pada saat ini, kita dapat menggunakan metode pengumpulan yang disediakan oleh stream, misalnya:
public static void streamCollectTest () {list <string> list = lists.stream (). filter ((p) -> p.startswith ("a")). disortir (). collect (collectors.tolist ()); System.out.println (daftar); }Menghitung
Jumlah seperti SQL digunakan untuk menghitung jumlah total elemen dalam aliran, misalnya:
public static void streamCountTest () {long count = lists.stream (). filter ((s -> s.startswith ("a"))). count (); System.out.println (Count); }Mengurangi
Metode reduce memungkinkan kita untuk menghitung elemen dengan cara kita sendiri atau mengaitkan elemen dalam aliran dengan beberapa pola, misalnya:
public static void streamreducetest () {opsional <string> opsional = lists.stream (). disortir (). reduksi ((s1, s2) -> {system.out.println (s1 + "|" + s2); return s1 + "|" + s2;}); }Hasil eksekusi adalah sebagai berikut:
A1 | A2A1 | A2 | B1A1 | A2 | B1 | B2A1 | A2 | B1 | B2 | B3A1 | A2 | B1 | B2 | B3 | O1
Aliran Paralel vs Serial Stream
Sejauh ini, kami telah memperkenalkan operasi menengah dan lengkap yang umum digunakan. Tentu saja semua contoh didasarkan pada aliran serial. Selanjutnya, kami akan memperkenalkan drama utama - aliran paralel (aliran paralel). Aliran paralel diimplementasikan berdasarkan kerangka dekomposisi paralel fork-join, dan membagi data besar yang ditetapkan menjadi beberapa data kecil dan menyerahkannya ke utas yang berbeda untuk diproses. Dengan cara ini, kinerja akan sangat ditingkatkan di bawah situasi pemrosesan multi-core. Ini konsisten dengan konsep desain MapReduce: tugas -tugas besar menjadi lebih kecil, dan tugas -tugas kecil dipindahkan ke mesin yang berbeda untuk dieksekusi. Tetapi tugas kecil di sini diserahkan kepada prosesor yang berbeda.
Buat aliran paralel melalui parallelStream() . Untuk memverifikasi apakah aliran paralel benar -benar dapat meningkatkan kinerja, kami menjalankan kode uji berikut:
Pertama -tama buat koleksi yang lebih besar:
Daftar <String> BigList = ArrayList baru <> (); untuk (int i = 0; i <10000000; i ++) {uuid uuid = uuid.randomuuid (); biglist.add (uuid.tostring ()); }Uji waktu untuk mengurutkan di bawah aliran serial:
private static void notparallelstreamSortedTest (daftar <string> biglists) {long starttime = system.nanoTime (); jumlah panjang = biglist.stream (). disortir (). count (); Long endtime = System.nanoTime (); long millis = timeunit.nanoseconds.tomillis (endtime - startTime); System.out.println (System.out.printf ("Serial Sort: %D MS", Millis)); }Uji waktu untuk mengurutkan dalam aliran paralel:
private static void parallelstreamSortedTest (daftar <string> biglists) {long starttime = system.nanoTime (); jumlah panjang = biglist.parallelstream (). sorted (). count (); Long endtime = System.nanoTime (); long millis = timeunit.nanoseconds.tomillis (endtime - startTime); System.out.println (System.out.printf ("Parallelsorting: %d ms", millis)); }Hasilnya adalah sebagai berikut:
Jenis Serial: 13336 ms
Jenis Paralel: 6755 ms
Setelah melihat ini, kami menemukan bahwa kinerja telah meningkat sekitar 50%. Apakah Anda juga berpikir bahwa Anda akan dapat menggunakan parallel Stream di masa depan? Bahkan, bukan itu masalahnya. Jika Anda masih prosesor inti tunggal sekarang dan volume data tidak besar, streaming serial masih merupakan pilihan yang baik. Anda juga akan menemukan bahwa dalam beberapa kasus, kinerja aliran serial lebih baik. Adapun penggunaan spesifik, Anda perlu mengujinya terlebih dahulu dan kemudian memutuskan sesuai dengan skenario yang sebenarnya.
Operasi malas
Di atas kami berbicara tentang aliran sungai selambat mungkin, dan di sini kami menjelaskannya dengan membuat aliran yang tak terbatas:
Pertama, gunakan Metode generate Stream untuk membuat urutan bilangan alami, dan kemudian mengubah aliran melalui map :
// kelas urutan tambahan natureseQ mengimplementasikan pemasok <long> {long value = 0; @Override public long get () {value ++; nilai pengembalian; }} public void streamCreateTest () {stream <long> stream = stream.generate (natureeseq ()); System.out.println ("Jumlah elemen:"+stream.map ((param) -> {return param;}). Limit (1000) .count ()); }Hasil eksekusi adalah:
Jumlah elemen: 1000
Kami menemukan bahwa pada awalnya, operasi perantara apa pun (seperti filter,map , dll., Tetapi sorted tidak dapat dilakukan) baik -baik saja. Artinya, proses melakukan operasi perantara pada aliran dan selamat dari aliran baru tidak segera berlaku (atau operasi map dalam contoh ini akan berjalan selamanya dan diblokir), dan aliran mulai menghitung ketika metode penyelesaian ditemui. Melalui metode limit() , ubah aliran tak terbatas ini menjadi aliran yang terbatas.
Meringkaskan
Di atas adalah semua isi dari pengantar cepat untuk Java Lambda. Setelah membaca artikel ini, apakah Anda memiliki pemahaman yang lebih dalam tentang Java Lambda? Saya harap artikel ini akan membantu semua orang untuk belajar Java Lambda.