Bab 2: Penggunaan Koleksi
Kita sering menggunakan berbagai koleksi, angka, string dan objek. Mereka ada di mana-mana, dan meskipun kode yang mengoperasikan koleksi dapat sedikit dioptimalkan, ini akan membuat kode menjadi lebih jelas. Dalam bab ini, kita mengeksplorasi cara menggunakan ekspresi lambda untuk memanipulasi koleksi. Kami menggunakannya untuk melintasi koleksi, mengubah koleksi menjadi koleksi baru, menghapus elemen dari koleksi, dan menggabungkan koleksi.
Lintasi daftarnya
Melintasi daftar adalah operasi himpunan paling dasar, dan operasinya telah mengalami beberapa perubahan selama bertahun-tahun. Kami menggunakan contoh kecil penelusuran nama, memperkenalkannya dari versi terlama hingga versi paling elegan saat ini.
Kita dapat dengan mudah membuat daftar nama yang tidak dapat diubah dengan kode berikut:
Copy kode kodenya sebagai berikut:
Daftar terakhir<String> teman =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
System.out.println(teman.get(i));
}
Berikut ini adalah metode paling umum untuk melintasi daftar dan mencetaknya, meskipun ini juga yang paling umum:
Copy kode kodenya sebagai berikut:
for(int i = 0; i < teman.ukuran(); i++) {
System.out.println(teman.get(i));
}
Saya menyebut cara penulisan ini masokistis--bertele-tele dan rawan kesalahan. Kita harus berhenti dan memikirkannya, "Apakah i< atau i<=?" Ini hanya masuk akal ketika kita perlu mengoperasikan elemen tertentu, namun meskipun demikian, kita masih dapat menggunakan ekspresi fungsional yang mematuhi prinsip kekekalan.gaya, yang akan kita bahas segera.
Java juga menyediakan struktur yang relatif maju.
Copy kode kodenya sebagai berikut:
koleksi/fpij/Iterasi.java
for(String nama : teman) {
System.out.println(nama);
}
Di bawah tenda, iterasi dengan cara ini diimplementasikan menggunakan antarmuka Iterator, memanggil metode hasNext dan next. Kedua metode tersebut merupakan iterator eksternal, dan keduanya menggabungkan cara melakukannya dengan apa yang ingin Anda lakukan. Kami secara eksplisit mengontrol iterasi, memberi tahu di mana harus memulai dan di mana harus mengakhiri; versi kedua melakukan ini melalui metode Iterator. Dalam operasi eksplisit, Anda juga dapat menggunakan pernyataan break dan continue untuk mengontrol iterasi. Versi kedua memiliki beberapa hal yang hilang dari versi pertama. Pendekatan ini lebih baik daripada yang pertama jika kita tidak bermaksud mengubah elemen koleksi. Namun, kedua metode ini sangat penting dan harus ditinggalkan di Java saat ini. Ada beberapa alasan untuk beralih ke gaya fungsional:
1. Perulangan for itu sendiri bersifat serial dan sulit untuk diparalelkan.
2. Lingkaran seperti itu bersifat non-polimorfik; apa yang Anda dapatkan adalah apa yang Anda minta. Kami meneruskan koleksi secara langsung ke perulangan for, daripada memanggil metode (yang mendukung polimorfisme) pada koleksi untuk melakukan operasi tertentu.
3. Dari sudut pandang desain, kode yang ditulis dengan cara ini melanggar prinsip "Katakan, Jangan Tanya". Kami meminta agar iterasi dilakukan daripada menyerahkan iterasi ke perpustakaan yang mendasarinya.
Saatnya beralih dari pemrograman imperatif lama ke pemrograman fungsional iterator internal yang lebih elegan. Setelah menggunakan iterator internal, kami menyerahkan banyak operasi spesifik ke pustaka metode yang mendasarinya untuk dieksekusi, sehingga Anda dapat lebih fokus pada kebutuhan bisnis tertentu. Fungsi yang mendasarinya akan bertanggung jawab untuk iterasi. Kami pertama kali menggunakan iterator internal untuk menghitung daftar nama.
Antarmuka Iterable telah ditingkatkan di JDK8. Ia memiliki nama khusus yang disebut forEach, yang menerima parameter tipe Comsumer. Seperti namanya, instance Konsumen menggunakan objek yang diteruskan melalui metode penerimaannya. Kami menggunakan sintaks kelas dalam anonim yang familier untuk menggunakan metode forEach:
Copy kode kodenya sebagai berikut:
friends.forEach(Konsumen baru<String>() { public void menerima(nama String akhir) {
Sistem.keluar.println(nama }
});
Kami memanggil metode forEach pada koleksi teman, meneruskannya sebagai implementasi anonim dari Konsumen. Metode forEach ini memanggil metode penerimaan yang diteruskan di Consumer untuk setiap elemen dalam koleksi, sehingga memungkinkannya memproses elemen ini. Dalam contoh ini kita hanya mencetak nilainya, yaitu namanya. Mari kita lihat keluaran versi ini, yang sama dengan dua versi sebelumnya:
Copy kode kodenya sebagai berikut:
Brian
Nate
Neal
Raja
Sara
Scott
Kami hanya mengubah satu hal: kami membuang loop for yang sudah usang dan menggunakan iterator internal baru. Keuntungannya adalah kita tidak perlu menentukan cara melakukan iterasi pada koleksi dan bisa lebih fokus pada cara memproses setiap elemen. Kerugiannya adalah kode terlihat lebih bertele-tele - yang hampir menghilangkan kesenangan dari gaya pengkodean baru. Untungnya, hal ini mudah untuk diubah, dan di sinilah kekuatan ekspresi lambda dan kompiler baru berperan. Mari buat satu modifikasi lagi dan ganti kelas dalam anonim dengan ekspresi lambda.
Copy kode kodenya sebagai berikut:
friends.forEach((nama String akhir) -> System.out.println(nama));
Ini terlihat jauh lebih baik dengan cara ini. Kodenya lebih sedikit, tapi mari kita lihat dulu apa maksudnya. Metode forEach adalah fungsi tingkat tinggi yang menerima ekspresi lambda atau blok kode untuk beroperasi pada elemen dalam daftar. Pada setiap panggilan, elemen dalam koleksi akan terikat pada variabel nama. Perpustakaan yang mendasarinya menampung aktivitas pemanggilan ekspresi lambda. Ia dapat memutuskan untuk menunda eksekusi ekspresi dan, jika perlu, melakukan komputasi paralel. Output versi ini juga sama dengan versi sebelumnya.
Copy kode kodenya sebagai berikut:
Brian
Nate
Neal
Raja
Sara
Scott
Versi iterator internal lebih ringkas. Selain itu, dengan menggunakannya, kita dapat lebih fokus pada pemrosesan setiap elemen daripada melintasinya - ini bersifat deklaratif.
Namun versi ini mempunyai kekurangan. Setelah metode forEach mulai dijalankan, tidak seperti dua versi lainnya, kami tidak dapat keluar dari iterasi ini. (Tentu saja ada cara lain untuk melakukan ini). Oleh karena itu, cara penulisan ini lebih umum digunakan ketika setiap elemen dalam koleksi perlu diproses. Nanti kita akan memperkenalkan beberapa fungsi lain yang memungkinkan kita mengontrol proses loop.
Sintaks standar untuk ekspresi lambda adalah memasukkan parameter ke dalam (), memberikan informasi tipe, dan menggunakan koma untuk memisahkan parameter. Untuk membebaskan kita, kompiler Java juga dapat secara otomatis melakukan pengurangan tipe. Tentu saja akan lebih nyaman untuk tidak menulis tipe. Lebih sedikit pekerjaan dan dunia lebih tenang. Berikut versi sebelumnya setelah menghilangkan tipe parameter:
Copy kode kodenya sebagai berikut:
friends.forEach((nama) -> System.out.println(nama));
Dalam contoh ini, kompiler Java mengetahui bahwa tipe namanya adalah String melalui analisis konteks. Itu melihat tanda tangan dari metode yang dipanggil forEach dan kemudian menganalisis antarmuka fungsional dalam parameter. Kemudian akan menganalisis metode abstrak di antarmuka ini dan memeriksa jumlah dan jenis parameter. Meskipun ekspresi lambda ini menerima beberapa parameter, kita masih dapat melakukan pengurangan tipe, tetapi dalam hal ini semua parameter tidak dapat memiliki tipe parameter; dalam ekspresi lambda, tipe parameter harus ditulis sama sekali, atau jika ditulis, harus ditulis sepenuhnya.
Kompiler Java memperlakukan ekspresi lambda dengan satu parameter secara khusus: jika Anda ingin melakukan inferensi tipe, tanda kurung di sekitar parameter dapat dihilangkan.
Copy kode kodenya sebagai berikut:
friends.forEach(nama -> System.out.println(nama));
Ada peringatan kecil di sini: parameter yang digunakan untuk inferensi tipe bukanlah tipe akhir. Pada contoh sebelumnya yang mendeklarasikan suatu tipe secara eksplisit, kami juga menandai parameter sebagai final. Ini mencegah Anda mengubah nilai parameter dalam ekspresi lambda. Secara umum, mengubah nilai suatu parameter merupakan kebiasaan buruk, yang dapat dengan mudah menyebabkan BUG, jadi sebaiknya menandainya sebagai final. Sayangnya, jika kita ingin menggunakan inferensi tipe, kita harus mengikuti aturannya sendiri dan tidak mengubah parameternya, karena kompiler tidak lagi melindungi kita.
Butuh banyak usaha untuk mencapai titik ini, tetapi sekarang jumlah kodenya memang sedikit lebih kecil. Namun ini bukanlah yang paling sederhana. Mari kita coba versi minimalis terakhir ini.
Copy kode kodenya sebagai berikut:
teman.forEach(System.out::println);
Dalam kode di atas kita menggunakan referensi metode. Kita bisa langsung mengganti seluruh kode dengan nama metode. Kita akan mengeksplorasi hal ini secara mendalam di bagian selanjutnya, namun untuk saat ini mari kita ingat kutipan terkenal dari Antoine de Saint-Exupéry: Kesempurnaan bukanlah apa yang bisa ditambahkan, tapi apa yang tidak bisa dihilangkan lagi.
Ekspresi Lambda memungkinkan kita menelusuri koleksi secara ringkas dan jelas. Di bagian berikutnya, kita akan membahas bagaimana hal ini memungkinkan kita menulis kode ringkas saat melakukan operasi penghapusan dan konversi pengumpulan.