Anotasi terjemahan: peta (pemetaan) dan pengurangan (pengurangan, penyederhanaan) adalah dua konsep yang sangat mendasar dalam matematika. Konsep tersebut telah lama muncul dalam berbagai bahasa pemrograman fungsional menerapkannya Setelah komputasi paralel diimplementasikan dalam sistem terdistribusi, nama kombinasi ini mulai bersinar di dunia komputer (para penggemar fungsional mungkin tidak berpikir demikian). Pada artikel ini, kita akan melihat debut kombinasi peta dan pengurangan setelah Java 8 mendukung pemrograman fungsional (ini hanya pengenalan awal, akan ada topik khusus tentangnya nanti).
Kurangi satu set
Sejauh ini kami telah memperkenalkan beberapa teknik baru untuk mengoperasikan koleksi: menemukan elemen yang cocok, menemukan elemen individual, dan transformasi koleksi. Operasi-operasi ini memiliki satu kesamaan, semuanya beroperasi pada satu elemen dalam koleksi. Tidak perlu membandingkan elemen atau melakukan operasi pada dua elemen. Di bagian ini kita melihat cara membandingkan elemen dan mempertahankan hasil operasi secara dinamis selama traversal kumpulan.
Mari kita mulai dengan contoh-contoh sederhana dan kemudian melanjutkan ke tahap selanjutnya. Pada contoh pertama, pertama-tama kita melakukan iterasi melalui koleksi teman dan menghitung jumlah total karakter di semua nama.
Copy kode kodenya sebagai berikut:
System.out.println("Jumlah total karakter di semua nama: " + friends.stream()
.mapToInt(nama -> nama.panjang())
.jumlah());
Untuk menghitung jumlah karakter kita perlu mengetahui panjang setiap nama. Hal ini dapat dengan mudah dicapai melalui metode mapToInt(). Setelah kita mengubah nama menjadi panjang yang sesuai, kita hanya perlu menjumlahkannya pada akhirnya. Kami memiliki metode sum() bawaan untuk mencapai hal ini. Inilah hasil akhirnya:
Copy kode kodenya sebagai berikut:
Jumlah total karakter di semua nama: 26
Kami menggunakan varian operasi peta, metode mapToInt() (seperti mapToInt, mapToDouble, dll., yang akan menghasilkan jenis aliran tertentu, seperti IntStream, DoubleStream), dan kemudian menghitung jumlah total karakter berdasarkan pada panjang kembali.
Selain menggunakan metode penjumlahan, masih banyak lagi metode serupa yang dapat digunakan, seperti max() untuk mencari panjang maksimum, min() untuk mencari panjang minimum, sortir() untuk mengurutkan panjang, rata-rata() untuk temukan panjang rata-rata, dll. tunggu.
Aspek menarik lainnya dari contoh di atas adalah mode MapReduce yang semakin populer. Metode map() melakukan pemetaan, dan metode sum() adalah operasi pengurangan yang umum digunakan. Faktanya, implementasi metode sum() di JDK menggunakan metode pengurangan(). Mari kita lihat beberapa bentuk operasi pengurangan yang lebih umum digunakan.
Misalnya, kami mengulangi semua nama dan mencetak nama dengan nama terpanjang. Jika ada beberapa nama yang terpanjang, kita cetak yang ditemukan terlebih dahulu. Salah satu caranya adalah dengan menghitung panjang maksimum dan kemudian memilih elemen pertama yang cocok dengan panjang tersebut. Namun, melakukan hal ini memerlukan penelusuran daftar dua kali - yang terlalu tidak efisien. Di sinilah operasi pengurangan berperan.
Kita dapat menggunakan operasi pengurangan untuk membandingkan panjang dua elemen, lalu mengembalikan elemen terpanjang, dan selanjutnya membandingkannya dengan elemen lainnya. Seperti fungsi tingkat tinggi lainnya yang kita lihat sebelumnya, metode pengurangan() juga melintasi seluruh koleksi. Antara lain, ini mencatat hasil penghitungan yang dikembalikan oleh ekspresi lambda. Jika ada contoh yang dapat membantu kita memahami hal ini dengan lebih baik, mari kita lihat potongan kodenya terlebih dahulu.
Copy kode kodenya sebagai berikut:
final Opsional<String> aLongName = friends.stream()
.reduce((nama1, nama2) ->
nama1.panjang() >= nama2.panjang() ? nama1 : nama2);
aLongName.ifPresent(nama ->
System.out.println(String.format("Nama terpanjang: %s", nama)));
Ekspresi lambda yang diteruskan ke metode pengurangan() menerima dua parameter, nama1 dan nama2, lalu membandingkan panjangnya dan mengembalikan yang terpanjang. Metode pengurangan() tidak tahu apa yang akan kita lakukan. Logika ini dihilangkan ke dalam ekspresi lambda yang kami berikan - ini adalah implementasi ringan dari pola Strategi.
Ekspresi lambda ini dapat disesuaikan dengan metode penerapan antarmuka fungsional BinaryOperator di JDK. Ini adalah jenis argumen yang diterima oleh metode pengurangan. Mari kita jalankan metode pengurangan ini dan lihat apakah metode ini dapat memilih nama pertama dari dua nama terpanjang dengan benar.
Copy kode kodenya sebagai berikut:
Nama terpanjang: Brian
Ketika metode pengurangan() melintasi koleksi, pertama-tama ia akan memanggil ekspresi lambda pada dua elemen pertama koleksi, dan hasil yang dikembalikan oleh panggilan tersebut akan terus digunakan untuk panggilan berikutnya. Pada panggilan kedua, nilai nama1 terikat dengan hasil panggilan sebelumnya, dan nilai nama2 adalah elemen ketiga dari koleksi. Elemen lainnya juga dipanggil dalam urutan ini. Hasil dari pemanggilan ekspresi lambda terakhir adalah hasil yang dikembalikan oleh seluruh metode pengurangan().
Metode pengurangan() mengembalikan nilai Opsional karena koleksi yang diteruskan ke sana mungkin kosong. Dalam hal ini, tidak akan ada nama terpanjang. Jika daftar hanya memiliki satu elemen, metode pengurangan secara langsung mengembalikan elemen tersebut dan tidak memanggil ekspresi lambda.
Dari contoh ini kita dapat menyimpulkan bahwa hasil pengurangan hanya dapat berupa paling banyak satu elemen dalam himpunan. Jika kita ingin mengembalikan nilai default atau nilai dasar, kita dapat menggunakan varian metode pengurangan() yang menerima parameter tambahan. Misalnya, jika nama terpendeknya adalah Steve, kita bisa meneruskannya ke metode pengurangan() seperti ini:
Copy kode kodenya sebagai berikut:
String terakhir steveOrLonger = teman.stream()
.reduce("Steve", (nama1, nama2) ->
nama1.panjang() >= nama2.panjang() ? nama1 : nama2);
Jika ada nama yang lebih panjang dari itu, maka nama ini akan dipilih; jika tidak, nilai dasar Steve akan dikembalikan. Versi metode pengurangan() ini tidak mengembalikan objek Opsional, karena jika koleksi kosong, nilai default akan dikembalikan terlepas dari kasus di mana tidak ada nilai kembalian.
Sebelum kita mengakhiri bab ini, mari kita lihat operasi yang sangat mendasar namun tidak mudah dalam operasi himpunan: menggabungkan elemen.
Gabungkan elemen
Kita telah mempelajari cara menemukan elemen, melintasi, dan mengonversi koleksi. Namun, ada operasi umum lainnya - penyambungan elemen koleksi - tanpa fungsi join() yang baru ditambahkan ini, kode ringkas dan elegan yang disebutkan sebelumnya akan sia-sia. Metode sederhana ini sangat praktis sehingga menjadi salah satu fungsi yang paling umum digunakan di JDK. Mari kita lihat cara menggunakannya untuk mencetak elemen dalam daftar, dipisahkan dengan koma.
Kami masih menggunakan daftar teman ini. Jika Anda menggunakan cara lama di perpustakaan JDK, apa yang harus Anda lakukan jika ingin mencetak semua nama yang dipisahkan dengan koma?
Kita harus mengulangi daftar dan mencetak elemen satu per satu. Perulangan for di Java 5 lebih baik dari yang sebelumnya, jadi mari kita gunakan.
Copy kode kodenya sebagai berikut:
for(String nama : teman) {
System.out.print(nama + ", ");
}
Sistem.keluar.println();
Kodenya sangat sederhana, mari kita lihat apa outputnya.
Copy kode kodenya sebagai berikut:
Brian, Nate, Neal, Raju, Sara, Scott,
Sial, ada koma yang menjengkelkan di akhir (bisakah kita menyalahkan Scott di akhir?). Bagaimana saya bisa memberitahu Java untuk tidak memberi koma di sini? Sayangnya, perulangan tersebut dijalankan selangkah demi selangkah, dan tidak mudah untuk melakukan sesuatu yang istimewa pada akhirnya. Untuk mengatasi masalah ini, kita dapat menggunakan metode loop asli.
Copy kode kodenya sebagai berikut:
for(int i = 0; i < teman.ukuran() - 1; i++) {
System.out.print(teman.dapatkan(i) + ", ");
}
if(teman.ukuran() > 0)
System.out.println(teman.dapatkan(teman.ukuran() - 1));
Mari kita lihat apakah keluaran versi ini OK.
Copy kode kodenya sebagai berikut:
Brian, Nate, Neal, Raju, Sara, Scott
Hasilnya masih bagus, tapi kode ini kurang bagus. Selamatkan kami, Jawa.
Kami tidak perlu menanggung rasa sakit ini lagi. Kelas StringJoiner di Java 8 membantu kita mengatasi masalah tersebut. Tidak hanya itu, kelas String juga menambahkan metode join sehingga kita bisa mengganti hal-hal di atas dengan satu baris kode.
Copy kode kodenya sebagai berikut:
System.out.println(String.join(", ", teman));
Coba lihat, hasilnya sama memuaskannya dengan kodenya.
Copy kode kodenya sebagai berikut:
Brian, Nate, Neal, Raju, Sara, Scott
Hasilnya masih bagus, tapi kode ini kurang bagus. Selamatkan kami, Jawa.
Kami tidak perlu menanggung rasa sakit ini lagi. Kelas StringJoiner di Java 8 membantu kita mengatasi masalah tersebut. Tidak hanya itu, kelas String juga menambahkan metode join sehingga kita bisa mengganti hal-hal di atas dengan satu baris kode.
Copy kode kodenya sebagai berikut:
System.out.println(String.join(", ", teman));
Coba lihat, hasilnya sama memuaskannya dengan kodenya.
Copy kode kodenya sebagai berikut:
Brian, Nate, Neal, Raju, Sara, Scott
Dalam implementasi dasarnya, metode String.join() memanggil kelas StringJoiner untuk menggabungkan nilai yang diteruskan sebagai parameter kedua (yang merupakan parameter panjang variabel) ke dalam string panjang, menggunakan parameter pertama sebagai pembatas. Tentu saja, metode ini lebih dari sekadar menggabungkan koma. Misalnya, kita dapat meneruskan banyak jalur dan dengan mudah mengeja jalur kelas, berkat metode dan kelas yang baru ditambahkan ini.
Kita sudah mengetahui cara menghubungkan elemen daftar. Sebelum menghubungkan daftar, kita juga dapat mengubah elemen. Tentu saja, kita juga mengetahui cara menggunakan metode peta untuk mengubah daftar. Selanjutnya, kita juga bisa menggunakan metode filter() untuk memfilter elemen yang kita inginkan. Langkah terakhir dalam menghubungkan elemen daftar, menggunakan koma atau pembatas lainnya, hanyalah operasi pengurangan sederhana.
Kita dapat menggunakan metode pengurangan() untuk menggabungkan elemen menjadi sebuah string, namun hal ini memerlukan usaha dari pihak kita. JDK memiliki metode kumpulkan() yang sangat nyaman, yang juga merupakan varian dari pengurangan(). Kita dapat menggunakannya untuk menggabungkan elemen menjadi nilai yang diinginkan.
Metode Collect() melakukan operasi reduksi, namun mendelegasikan operasi spesifik ke kolektor untuk dieksekusi. Kita dapat menggabungkan elemen yang dikonversi menjadi ArrayList. Melanjutkan contoh sebelumnya, kita dapat menggabungkan elemen yang dikonversi menjadi string yang dipisahkan koma.
Copy kode kodenya sebagai berikut:
Sistem.keluar.println(
teman.stream()
.map(String::toUpperCase)
.collect(bergabung(", ")));
Kami memanggil metode Collect() pada daftar yang dikonversi, meneruskannya ke kolektor yang dikembalikan oleh metode join(). Bergabung adalah metode statis di kelas alat Kolektor. Kolektor itu seperti penerima. Ia menerima objek yang diteruskan dengan mengumpulkan dan menyimpannya dalam format yang Anda inginkan: ArrayList, String, dll. Kita akan mengeksplorasi metode ini lebih jauh dalam metode pengumpulan dan kelas Kolektor di halaman 52.
Ini adalah nama keluarannya, sekarang menjadi huruf besar dan dipisahkan dengan koma.
Copy kode kodenya sebagai berikut:
BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
Meringkaskan
Koleksi sangat umum dalam pemrograman. Dengan ekspresi lambda, operasi pengumpulan Java menjadi lebih sederhana dan mudah. Semua kode lama yang kikuk untuk operasi pengumpulan dapat diganti dengan pendekatan baru yang elegan dan ringkas ini. Iterator internal membuat traversal dan transformasi koleksi menjadi lebih nyaman, jauh dari masalah variabilitas, dan menemukan elemen koleksi menjadi sangat mudah. Anda dapat menulis lebih sedikit kode menggunakan metode baru ini. Hal ini membuat kode lebih mudah dipelihara, lebih fokus pada logika bisnis, dan mengurangi operasi dasar dalam pemrograman.
Pada bab selanjutnya kita akan melihat bagaimana ekspresi lambda menyederhanakan operasi dasar lainnya dalam pengembangan program: manipulasi string dan perbandingan objek.