Bab 1 Halo, ekspresi lambda!
Bagian 1
Gaya pengkodean Java sedang menghadapi perubahan besar.
Pekerjaan kita sehari-hari akan menjadi lebih sederhana, nyaman dan ekspresif. Java, metode pemrograman baru, telah muncul di bahasa pemrograman lain beberapa dekade yang lalu. Setelah fitur-fitur baru ini diperkenalkan ke dalam Java, kita dapat menulis kode yang lebih ringkas, elegan, lebih ekspresif, dan memiliki lebih sedikit kesalahan. Kita dapat menerapkan berbagai strategi dan pola desain dengan lebih sedikit kode.
Dalam buku ini kita akan mengeksplorasi pemrograman gaya fungsional melalui contoh-contoh dari pemrograman sehari-hari. Sebelum menggunakan cara desain dan kode yang baru dan elegan ini, mari kita lihat terlebih dahulu apa kelebihannya.
mengubah cara berpikir Anda
Gaya Imperatif - Ini adalah pendekatan yang disediakan bahasa Java sejak awal. Dengan menggunakan gaya ini, kita harus memberi tahu Java apa yang harus dilakukan pada setiap langkah, dan kemudian menyaksikan Java mengeksekusinya langkah demi langkah. Ini tentu saja bagus, tapi sepertinya agak mendasar. Kodenya terlihat sedikit bertele-tele, dan kita berharap bahasanya bisa menjadi sedikit lebih pintar. Kita harus memberitahukannya apa yang kita inginkan daripada memberitahukannya bagaimana cara melakukannya; Untungnya, Java akhirnya dapat membantu kita mewujudkan keinginan tersebut. Mari kita lihat beberapa contoh untuk memahami kelebihan dan perbedaan gaya ini.
cara biasa
Mari kita mulai dengan dua contoh umum. Ini adalah metode perintah untuk memeriksa apakah Chicago ada dalam koleksi kota yang ditentukan - ingat, kode yang tercantum dalam buku ini hanya sebagian saja.
Copy kode kodenya sebagai berikut:
boolean ditemukan = salah;
for(String kota : kota) {
if(kota.sama dengan("Chicago")) {
ditemukan = benar;
merusak;
}
}
System.out.println("Ditemukan chicago?:" + ditemukan);
Versi imperatif ini terlihat agak bertele-tele dan sederhana; versi ini dibagi menjadi beberapa bagian eksekusi. Pertama, inisialisasi tag Boolean yang disebut ditemukan, lalu lintasi setiap elemen dalam koleksi; jika kota yang kita cari ditemukan, atur tag ini, lalu keluar dari loop;
cara yang lebih baik
Setelah membaca kode ini, pemrogram Java yang cermat akan segera memikirkan cara yang lebih ringkas dan jelas, seperti ini:
Copy kode kodenya sebagai berikut:
System.out.println("Ditemukan chicago?:" + city.contains("Chicago"));
Ini juga merupakan gaya penulisan yang penting - metode berisi melakukannya untuk kita secara langsung.
perbaikan yang sebenarnya
Menulis kode seperti ini memiliki beberapa keuntungan:
1. Tidak perlu lagi mengotak-atik variabel yang bisa berubah itu
2. Enkapsulasi iterasi ke lapisan bawah
3. Kodenya lebih sederhana
4. Kode lebih jelas dan fokus
5. Kurangi mengambil jalan memutar dan integrasikan kode dan kebutuhan bisnis secara lebih dekat
6. Kurang rawan kesalahan
7. Mudah dipahami dan dipelihara
Mari kita ambil contoh yang lebih rumit.
Contoh ini terlalu sederhana. Pertanyaan penting apakah suatu elemen ada dalam koleksi dapat dilihat di mana saja di Java. Sekarang asumsikan bahwa kita ingin menggunakan pemrograman imperatif untuk melakukan beberapa operasi tingkat lanjut, seperti mengurai file, berinteraksi dengan database, memanggil layanan WEB, pemrograman bersamaan, dll. Sekarang kita dapat menggunakan Java untuk menulis kode yang lebih ringkas, elegan, dan bebas kesalahan, tidak hanya dalam skenario sederhana ini.
cara lama
Mari kita lihat contoh lainnya. Kami menentukan kisaran harga dan menghitung total harga yang didiskon dengan cara yang berbeda.
Copy kode kodenya sebagai berikut:
Daftar akhir<BigDecimal> harga = Arrays.asList(
BigDecimal baru("10"), BigDecimal baru("30"), BigDecimal baru("17"),
BigDecimal baru("20"), BigDecimal baru("15"), BigDecimal baru("18"),
BigDecimal baru("45"), BigDecimal baru("12"));
Misalkan ada diskon 10% jika melebihi 20 yuan, mari kita terapkan dengan cara biasa dulu.
Copy kode kodenya sebagai berikut:
Total BigDecimalOfDiscountedPrices = BigDecimal.ZERO;
for(Harga Desimal Besar : harga) {
if(harga.bandingkanKe(Desimal Besar.nilai(20)) > 0)
totalHarga Diskon =
totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9)));
}
System.out.println("Total harga yang didiskon : "+totalHargaDiskon);
Kode ini harusnya sangat familiar; pertama gunakan variabel untuk menyimpan harga total; lalu ulangi semua harga, temukan harga yang lebih besar dari 20 yuan, hitung harga diskonnya, dan tambahkan ke harga total; harga setelah diskon.
Berikut ini keluaran programnya:
Copy kode kodenya sebagai berikut:
Total harga diskon: 67,5
Hasilnya sepenuhnya benar, tetapi kodenya agak berantakan. Bukan salah kami jika kami hanya bisa menulis sesuai keinginan kami. Namun, kode tersebut agak sederhana. Kode ini tidak hanya menderita paranoia tipe dasar, tetapi juga melanggar prinsip tanggung jawab tunggal. Jika Anda bekerja dari rumah dan memiliki anak yang ingin menjadi pembuat kode, Anda harus menyembunyikan kode Anda, kalau-kalau mereka melihatnya dan menghela nafas kecewa dan berkata, “Kamu mencari nafkah dengan melakukan ini?
Ada cara yang lebih baik
Kita bisa berbuat lebih baik – dan jauh lebih baik. Kode kita mirip dengan spesifikasi persyaratan. Hal ini dapat mempersempit kesenjangan antara persyaratan bisnis dan kode yang diterapkan, sehingga mengurangi kemungkinan salah tafsir persyaratan.
Kita tidak lagi membiarkan Java membuat variabel dan menetapkannya tanpa henti. Kita perlu berkomunikasi dengannya dari tingkat abstraksi yang lebih tinggi, seperti kode berikut.
Copy kode kodenya sebagai berikut:
total BigDecimal akhirHargaDiskon =
harga.stream()
.filter(harga -> harga.compareTo(BigDecimal.valueOf(20)) > 0)
.map(harga -> harga.multiply(BigDecimal.valueOf(0.9)))
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Total harga yang didiskon : "+totalHargaDiskon);
Bacalah dengan lantang - saring harga yang lebih besar dari 20 yuan, ubah menjadi harga diskon, lalu tambahkan. Kode ini persis sama dengan proses yang kami gunakan untuk menjelaskan kebutuhan kami. Di Java, juga sangat mudah untuk melipat baris kode yang panjang dan menyelaraskannya per baris sesuai dengan titik di depan nama metode, seperti di atas.
Kodenya sangat sederhana, tetapi kami menggunakan banyak hal baru di Java8. Pertama, kita sebut metode aliran daftar harga. Ini membuka pintu bagi iterator nyaman yang tak terhitung jumlahnya, yang akan kita bahas nanti.
Kami menggunakan beberapa metode khusus, seperti filter dan map, daripada menelusuri seluruh daftar secara langsung. Metode ini tidak seperti yang ada di JDK yang kita gunakan sebelumnya, metode ini menerima fungsi anonim-ekspresi lambda-sebagai parameter. (Kami akan membahas ini secara mendalam nanti). Kita memanggil metode pengurangan() untuk menghitung jumlah harga yang dikembalikan oleh metode map().
Sama seperti metode berisi, badan perulangan disembunyikan. Namun, metode peta (dan metode filter) jauh lebih rumit. Ini memanggil ekspresi lambda yang diteruskan untuk menghitung setiap harga dalam daftar harga, dan memasukkan hasilnya ke dalam koleksi baru. Terakhir kita memanggil metode pengurangan pada koleksi baru ini untuk mendapatkan hasil akhir.
Ini adalah output dari kode di atas:
Copy kode kodenya sebagai berikut:
Total harga diskon: 67,5
area untuk perbaikan
Ini merupakan peningkatan yang signifikan dibandingkan penerapan sebelumnya:
1. Terstruktur dengan baik tetapi tidak berantakan
2. Tidak ada operasi tingkat rendah
3. Mudah untuk meningkatkan atau memodifikasi logika
4. Iterasi berdasarkan perpustakaan metode
5. Efisien; evaluasi badan loop yang malas
6. Mudah diparalelkan
Di bawah ini kita akan membahas bagaimana Java mengimplementasikannya.
Ekspresi Lambda hadir untuk menyelamatkan dunia
Ekspresi Lambda adalah jalan pintas yang menyelamatkan kita dari masalah pemrograman imperatif. Fitur baru yang disediakan oleh Java ini telah mengubah metode pemrograman asli kami, menjadikan kode yang kami tulis tidak hanya ringkas dan elegan, tidak terlalu rawan kesalahan, tetapi juga lebih efisien, mudah untuk dioptimalkan, ditingkatkan, dan diparalelkan.
Bagian 2: Keuntungan terbesar dari pemrograman fungsional
Kode gaya fungsional memiliki rasio signal-to-noise yang lebih tinggi; lebih sedikit kode yang ditulis, tetapi lebih banyak yang dilakukan per baris atau ekspresi. Dibandingkan dengan pemrograman imperatif, pemrograman fungsional memiliki banyak manfaat bagi kita:
Modifikasi eksplisit atau penetapan variabel dihindari, yang sering kali menjadi sumber bug dan membuat kode sulit untuk diparalelkan. Dalam pemrograman baris perintah, kami terus memberikan nilai ke variabel totalOfDiscountedPrices di badan perulangan. Dalam gaya fungsional, kode tidak lagi mengalami operasi modifikasi eksplisit. Semakin sedikit variabel yang diubah, semakin sedikit bug yang dimiliki kode tersebut.
Kode gaya fungsional dapat dengan mudah diparalelkan. Jika komputasi memakan waktu, kita dapat dengan mudah menjalankan elemen daftar secara bersamaan. Jika kita ingin memparalelkan kode imperatif, kita juga harus mengkhawatirkan masalah yang disebabkan oleh modifikasi variabel totalOfDiscountedPrices secara bersamaan. Dalam pemrograman fungsional kami hanya mengakses variabel ini setelah diproses sepenuhnya, sehingga menghilangkan masalah keamanan thread.
Kode lebih ekspresif. Pemrograman imperatif dibagi menjadi beberapa langkah untuk menjelaskan apa yang harus dilakukan - membuat nilai inisialisasi, mengulangi harga, menambahkan harga diskon ke variabel, dll. - sedangkan pemrograman fungsional hanya perlu membuat metode peta dari daftar mengembalikan nilai termasuk diskon . Cukup buat daftar harga baru lalu kumpulkan.
Pemrograman fungsional lebih sederhana; lebih sedikit kode yang dibutuhkan untuk mencapai hasil yang sama dibandingkan pemrograman imperatif. Kode yang lebih bersih berarti lebih sedikit kode untuk ditulis, lebih sedikit untuk dibaca, dan lebih sedikit untuk dipelihara - lihat "Apakah kurang ringkas cukup untuk menjadi ringkas?"
Kode fungsional lebih intuitif - membaca kode seperti mendeskripsikan masalah - dan mudah dipahami setelah kita memahami sintaksisnya. Metode peta menjalankan fungsi yang diberikan (menghitung harga diskon) untuk setiap elemen koleksi, dan kemudian mengembalikan kumpulan hasil, seperti yang ditunjukkan pada gambar di bawah.
Gambar 1 - peta menjalankan fungsi yang diberikan pada setiap elemen dalam koleksi
Dengan ekspresi lambda, kita dapat memanfaatkan sepenuhnya kekuatan pemrograman fungsional di Java. Dengan menggunakan gaya fungsional, Anda dapat menulis kode yang lebih ekspresif, ringkas, memiliki lebih sedikit tugas, dan memiliki lebih sedikit kesalahan.
Dukungan untuk pemrograman berorientasi objek merupakan keuntungan utama Java. Pemrograman fungsional dan pemrograman berorientasi objek tidak berdiri sendiri-sendiri. Perubahan gaya yang nyata adalah dari pemrograman baris perintah ke pemrograman deklaratif. Di Java 8, fungsional dan berorientasi objek dapat diintegrasikan secara efektif. Kita dapat terus menggunakan gaya OOP untuk memodelkan entitas domain dan status serta hubungannya. Selain itu, kita juga dapat menggunakan fungsi untuk memodelkan transisi perilaku atau status, alur kerja dan pemrosesan data, serta membuat fungsi gabungan.
Bagian 3: Mengapa menggunakan gaya fungsional?
Kita telah melihat keuntungan dari pemrograman fungsional, tetapi apakah layak menggunakan gaya baru ini? Apakah ini hanya perbaikan kecil atau perubahan total? Masih banyak pertanyaan praktis yang perlu dijawab sebelum kita benar-benar meluangkan waktu untuk membahas hal ini.
Copy kode kodenya sebagai berikut:
Xiao Ming bertanya:
Apakah lebih sedikit kode berarti kesederhanaan?
Kesederhanaan berarti lebih sedikit tetapi tidak berantakan. Pada analisa akhir, berarti mampu mengungkapkan maksud secara efektif. Manfaatnya sangat luas.
Menulis kode ibarat menumpuk bahan menjadi satu. Kesederhanaan artinya bisa mencampurkan bahan menjadi bumbu. Menulis kode yang ringkas membutuhkan kerja keras. Kode yang harus dibaca lebih sedikit, dan kode yang benar-benar berguna transparan bagi Anda. Kode pendek yang sulit dipahami atau menyembunyikan detailnya lebih pendek daripada ringkas.
Kode sederhana sebenarnya berarti desain yang tangkas. Kode sederhana tanpa birokrasi. Ini berarti kita dapat dengan cepat mencoba ide-ide, melanjutkan jika ide tersebut berhasil, dan melompati dengan cepat jika ide tersebut tidak berhasil.
Menulis kode di Java tidaklah sulit dan sintaksnya sederhana. Dan kita sudah mengetahui dengan baik perpustakaan dan API yang ada. Yang paling sulit adalah menggunakannya untuk mengembangkan dan memelihara aplikasi tingkat perusahaan.
Kita perlu memastikan bahwa rekan-rekan menutup koneksi database pada waktu yang tepat, bahwa mereka tidak terus-menerus menduduki transaksi, bahwa pengecualian ditangani dengan benar pada lapisan yang sesuai, bahwa kunci diperoleh dan dilepaskan dengan benar, dll.
Jika dilihat secara individual, semua masalah ini bukanlah masalah besar. Namun jika ditambah dengan kompleksitas lapangan, permasalahan menjadi sangat sulit, sumber daya pengembangan terbatas, dan pemeliharaan sulit.
Apa yang akan terjadi jika kita merangkum strategi-strategi ini ke dalam banyak bagian kecil kode dan membiarkannya melakukan pengelolaan kendala secara mandiri? Maka kita tidak perlu terus-menerus mengeluarkan energi untuk menerapkan strategi. Ini merupakan kemajuan besar, mari kita lihat bagaimana pemrograman fungsional melakukannya.
Iterasi yang gila
Kami telah menulis berbagai iterasi untuk memproses daftar, set, dan peta. Menggunakan iterator di Java sangat umum, namun terlalu rumit. Tidak hanya memakan beberapa baris kode, namun juga sulit untuk diringkas.
Bagaimana cara kita menelusuri koleksi dan mencetaknya? Anda dapat menggunakan perulangan for. Bagaimana cara kita memfilter beberapa elemen dari koleksi? Masih menggunakan perulangan for, tetapi Anda perlu menambahkan beberapa variabel tambahan yang dapat dimodifikasi. Setelah memilih nilai-nilai tersebut, bagaimana cara menggunakannya untuk mencari nilai akhir, seperti nilai minimum, nilai maksimum, nilai rata-rata, dll? Kemudian Anda harus mendaur ulang dan memodifikasi variabel.
Iterasi semacam ini seperti obat mujarab, bisa melakukan segalanya, tapi semuanya jarang. Java sekarang menyediakan iterator bawaan untuk banyak operasi: misalnya, yang hanya melakukan perulangan, yang melakukan operasi pemetaan, yang memfilter nilai, yang melakukan operasi pengurangan, dan ada banyak fungsi yang mudah digunakan seperti maksimum, minimum, dan rata-rata. dll. Selain itu, operasi-operasi ini dapat digabungkan dengan baik, sehingga kita dapat menyatukannya untuk mengimplementasikan logika bisnis, yang sederhana dan memerlukan lebih sedikit kode. Selain itu, kode yang ditulis sangat mudah dibaca karena konsisten secara logis dengan urutan penjelasan masalahnya. Kita akan melihat beberapa contoh seperti itu di Bab 2, Menggunakan Koleksi, di halaman 19, dan buku ini penuh dengan contoh-contoh seperti itu.
Terapkan strategi
Kebijakan diterapkan di seluruh aplikasi perusahaan. Misalnya, kita perlu mengonfirmasi bahwa suatu operasi telah diautentikasi dengan benar demi keamanan, kita perlu memastikan bahwa transaksi dapat dieksekusi dengan cepat, dan log modifikasi diperbarui dengan benar. Tugas-tugas ini biasanya berakhir menjadi potongan kode biasa di sisi server, mirip dengan kodesemu berikut:
Copy kode kodenya sebagai berikut:
Transaksi transaksi = getFromTransactionFactory();
//... operasi yang dijalankan dalam transaksi ...
checkProgressAndCommitOrRollbackTransaction();
UpdateAuditTrail();
Ada dua masalah dengan pendekatan ini. Pertama, hal ini sering mengakibatkan duplikasi upaya dan juga meningkatkan biaya pemeliharaan. Kedua, mudah untuk melupakan pengecualian yang mungkin diberikan dalam kode bisnis, yang dapat mempengaruhi siklus hidup transaksi dan pembaruan log modifikasi. Ini harus diimplementasikan menggunakan blok try dan akhirnya, tetapi setiap kali seseorang menyentuh kode ini, kami harus memastikan kembali bahwa strategi ini belum dimusnahkan.
Ada cara lain, kita bisa menghapus pabrik dan meletakkan kode ini di depannya. Alih-alih mendapatkan objek transaksi, teruskan kode yang dieksekusi ke fungsi yang terpelihara dengan baik, seperti ini:
Copy kode kodenya sebagai berikut:
runWithinTransaction((Transaksi transaksi) -> {
//... operasi yang dijalankan dalam transaksi ...
});
Ini adalah langkah kecil bagi Anda, tetapi ini akan menyelamatkan Anda dari banyak masalah. Strategi memeriksa status dan memperbarui log pada saat yang sama disarikan dan dienkapsulasi ke dalam metode runWithinTransaction. Kami mengirimkan metode ini sepotong kode yang perlu dijalankan dalam konteks transaksi. Kita tidak perlu lagi khawatir seseorang lupa melakukan langkah ini atau tidak menangani pengecualian dengan benar. Fungsi yang melaksanakan kebijakan sudah menangani hal tersebut.
Kita akan membahas cara menggunakan ekspresi lambda untuk menerapkan strategi ini di Bab 5.
Strategi ekspansi
Strategi sepertinya ada dimana-mana. Selain menerapkannya, aplikasi perusahaan juga perlu memperluasnya. Kami berharap dapat menambah atau menghapus beberapa operasi melalui beberapa informasi konfigurasi. Dengan kata lain, kami dapat memprosesnya sebelum logika inti modul dijalankan. Hal ini sangat umum terjadi di Jawa, namun perlu dipikirkan dan dirancang terlebih dahulu.
Komponen yang perlu diperluas biasanya memiliki satu atau lebih antarmuka. Kita perlu merancang antarmuka dan struktur hierarki kelas implementasi dengan hati-hati. Ini mungkin bekerja dengan baik, tetapi ini akan memberi Anda banyak antarmuka dan kelas yang perlu dipertahankan. Desain seperti itu dapat dengan mudah menjadi berat dan sulit dirawat, yang pada akhirnya menggagalkan tujuan penskalaan.
Ada solusi lain - antarmuka fungsional dan ekspresi lambda, yang dapat kita gunakan untuk merancang strategi yang terukur. Kita tidak harus membuat antarmuka baru atau mengikuti nama metode yang sama. Kita bisa lebih fokus pada logika bisnis yang akan diimplementasikan, yang akan kami sebutkan dalam menggunakan ekspresi lambda untuk dekorasi di halaman 73.
Konkurensi menjadi mudah
Sebuah aplikasi besar sedang mendekati tonggak rilis ketika tiba-tiba muncul masalah kinerja yang serius. Tim dengan cepat menentukan bahwa hambatan kinerja terjadi pada modul besar yang memproses data dalam jumlah besar. Seseorang dalam tim menyarankan bahwa kinerja sistem dapat ditingkatkan jika keunggulan multi-core dapat dimanfaatkan sepenuhnya. Namun, jika modul besar ini ditulis dengan gaya Java lama, kegembiraan yang dibawa oleh saran ini akan segera hancur.
Tim segera menyadari bahwa mengubah raksasa ini dari eksekusi serial ke eksekusi paralel akan memerlukan banyak usaha, menambah kompleksitas ekstra, dan dengan mudah menyebabkan BUG terkait multi-threading. Bukankah ada cara yang lebih baik untuk meningkatkan kinerja?
Mungkinkah kode serial dan paralel sama, terlepas dari apakah Anda memilih eksekusi serial atau paralel, seperti menekan tombol dan mengekspresikan ide Anda?
Sepertinya ini hanya mungkin terjadi di Narnia, tapi jika kita mengembangkan sepenuhnya secara fungsional, semua ini akan menjadi kenyataan. Iterator bawaan dan gaya fungsional akan menghilangkan rintangan terakhir menuju paralelisasi. Desain JDK memungkinkan peralihan antara eksekusi serial dan paralel hanya dengan beberapa perubahan kode yang tidak mencolok, yang akan kami sebutkan di "Menyelesaikan Lompatan ke Paralelisasi" di halaman 145.
bercerita
Banyak hal yang hilang dalam proses mengubah persyaratan bisnis menjadi implementasi kode. Semakin banyak yang hilang, semakin tinggi kemungkinan kesalahan dan biaya pengelolaannya. Jika kodenya terlihat menjelaskan persyaratan, maka akan lebih mudah dibaca, lebih mudah berdiskusi dengan orang yang memenuhi persyaratan, dan akan lebih mudah untuk memenuhi kebutuhan mereka.
Misalnya, Anda mendengar manajer produk berkata, "Dapatkan harga semua saham, temukan saham yang harganya lebih dari 500 yuan, dan hitung total aset yang dapat membayar dividen." Dengan menggunakan fasilitas baru yang disediakan oleh Java, Anda dapat menulis:
Copy kode kodenya sebagai berikut:
tickers.map(StockUtil::getprice).filter(StockUtil::priceIsLessThan500).sum()
Proses konversi ini hampir tidak ada kerugiannya karena pada dasarnya tidak ada apa pun yang perlu dikonversi. Ini adalah gaya fungsional dalam tindakan, dan Anda akan melihat lebih banyak contohnya di seluruh buku ini, terutama Bab 8, Membangun Program dengan Ekspresi Lambda, halaman 137.
Fokus pada karantina
Dalam pengembangan sistem, bisnis inti dan logika halus yang diperlukan biasanya perlu diisolasi. Misalnya, sistem pemrosesan pesanan mungkin ingin menggunakan strategi perpajakan yang berbeda untuk sumber transaksi yang berbeda. Mengisolasi penghitungan pajak dari logika pemrosesan lainnya membuat kode lebih dapat digunakan kembali dan terukur.
Dalam pemrograman berorientasi objek kita menyebutnya isolasi perhatian, dan pola strategi biasanya digunakan untuk memecahkan masalah ini. Solusinya umumnya dengan membuat beberapa antarmuka dan kelas implementasi.
Kita dapat mencapai efek yang sama dengan lebih sedikit kode. Kita juga dapat dengan cepat mencoba ide produk kita sendiri tanpa harus memikirkan banyak kode dan mengalami stagnasi. Kita akan mempelajari lebih lanjut cara membuat pola ini dan melakukan isolasi kekhawatiran melalui fungsi ringan di Isolasi Kekhawatiran Menggunakan Ekspresi Lambda di halaman 63.
evaluasi malas
Saat mengembangkan aplikasi tingkat perusahaan, kami dapat berinteraksi dengan layanan WEB, memanggil database, memproses XML, dll. Ada banyak operasi yang perlu kita lakukan, namun tidak semuanya diperlukan setiap saat. Menghindari operasi tertentu atau setidaknya menunda beberapa operasi sementara yang tidak diperlukan adalah salah satu cara termudah untuk meningkatkan kinerja atau mengurangi waktu startup dan respons program.
Ini hanyalah hal kecil, tetapi membutuhkan banyak usaha untuk mengimplementasikannya dengan cara OOP murni. Untuk menunda inisialisasi beberapa objek kelas berat, kita harus menangani berbagai referensi objek, memeriksa pointer nol, dll.
Namun, jika Anda menggunakan kelas Optinal baru dan beberapa API gaya fungsional yang disediakannya, proses ini akan menjadi sangat sederhana dan kodenya akan lebih jelas. Kita akan membahas ini dalam inisialisasi lambat di halaman 105.
Meningkatkan kemampuan pengujian
Semakin sedikit logika pemrosesan yang dimiliki kode, semakin kecil kemungkinan kesalahan diperbaiki. Secara umum, kode fungsional lebih mudah dimodifikasi dan diuji.
Selain itu, seperti Bab 4, Mendesain dengan Ekspresi Lambda dan Bab 5, Menggunakan Sumber Daya, ekspresi lambda dapat digunakan sebagai objek tiruan ringan untuk membuat pengujian pengecualian lebih jelas dan mudah dipahami. Ekspresi Lambda juga dapat berfungsi sebagai alat bantu pengujian yang hebat. Banyak kasus uji umum yang dapat menerima dan menangani ekspresi lambda. Kasus uji yang ditulis dengan cara ini dapat menangkap esensi fungsionalitas yang perlu diuji regresi. Pada saat yang sama, berbagai implementasi yang perlu diuji dapat diselesaikan dengan meneruskan ekspresi lambda yang berbeda.
Kasus pengujian otomatis JDK juga merupakan contoh aplikasi ekspresi lambda yang bagus - jika Anda ingin tahu lebih banyak, Anda dapat melihat kode sumber di repositori OpenJDK. Melalui program pengujian ini, Anda dapat melihat bagaimana ekspresi lambda membuat parameter perilaku utama dari kasus pengujian; misalnya, mereka membangun program pengujian seperti ini, "Buat wadah untuk hasil", lalu "Tambahkan beberapa pemeriksaan kondisi pasca parameter".
Kita telah melihat bahwa pemrograman fungsional tidak hanya memungkinkan kita menulis kode berkualitas tinggi, tetapi juga memecahkan berbagai masalah dengan elegan selama proses pengembangan. Ini berarti pengembangan program akan menjadi lebih cepat dan mudah, dengan lebih sedikit kesalahan - selama Anda mengikuti beberapa panduan yang akan kami perkenalkan nanti.
Bagian 4: Evolusi bukan revolusi
Kita tidak perlu beralih ke bahasa lain untuk menikmati manfaat pemrograman fungsional; yang perlu kita ubah hanyalah cara kita menggunakan Java. Bahasa seperti C++, Java, dan C# semuanya mendukung pemrograman imperatif dan berorientasi objek. Namun kini mereka mulai menerapkan pemrograman fungsional. Kita baru saja melihat kedua gaya kode dan mendiskusikan manfaat yang dapat dihasilkan oleh pemrograman fungsional. Sekarang mari kita lihat beberapa konsep dan contoh utamanya untuk membantu kita mempelajari gaya baru ini.
Tim pengembangan bahasa Java telah menghabiskan banyak waktu dan tenaga untuk menambahkan kemampuan pemrograman fungsional ke bahasa Java dan JDK. Untuk menikmati manfaat yang dibawanya, kita harus memperkenalkan beberapa konsep baru terlebih dahulu. Kami dapat meningkatkan kualitas kode kami selama kami mengikuti aturan berikut:
1. Deklaratif
2. Mendorong kekekalan
3. Hindari efek samping
4. Lebih menyukai ekspresi daripada pernyataan
5. Desain menggunakan fungsi tingkat tinggi
Mari kita lihat pedoman praktis ini.
deklaratif
Inti dari apa yang kita kenal sebagai pemrograman imperatif adalah variabilitas dan pemrograman berbasis perintah. Kami membuat variabel dan kemudian terus mengubah nilainya. Kami juga memberikan instruksi rinci untuk dieksekusi, seperti membuat flag indeks dari iterasi, menaikkan nilainya, memeriksa apakah loop telah berakhir, memperbarui elemen ke-N dari array, dll. Di masa lalu, karena keterbatasan karakteristik alat dan perangkat keras, kami hanya dapat menulis kode dengan cara ini. Kita juga telah melihat bahwa pada koleksi yang tidak dapat diubah, metode isi deklaratif lebih mudah digunakan daripada metode imperatif. Semua masalah sulit dan operasi tingkat rendah diimplementasikan dalam fungsi perpustakaan, dan kita tidak perlu mengkhawatirkan detail ini lagi. Demi kesederhanaan, kita juga harus menggunakan pemrograman deklaratif. Pemrograman yang tidak dapat diubah dan deklaratif adalah inti dari pemrograman fungsional, dan kini Java akhirnya mewujudkannya.
Mempromosikan kekekalan
Kode dengan variabel yang bisa berubah akan memiliki banyak jalur aktivitas. Semakin banyak hal yang Anda ubah, semakin mudah menghancurkan struktur asli dan menimbulkan lebih banyak kesalahan. Kode dengan banyak variabel yang dimodifikasi sulit untuk dipahami dan sulit untuk diparalelkan. Kekekalan pada dasarnya menghilangkan kekhawatiran ini. Java mendukung kekekalan namun tidak memerlukannya - namun kami bisa. Kita perlu mengubah kebiasaan lama dalam mengubah keadaan objek. Kita harus menggunakan objek yang tidak dapat diubah sebanyak mungkin. Saat mendeklarasikan variabel, anggota, dan parameter, cobalah mendeklarasikannya sebagai final, seperti pepatah terkenal Joshua Bloch dalam "Java Efektif", "Perlakukan objek sebagai tidak dapat diubah". Saat membuat objek, cobalah untuk membuat objek yang tidak dapat diubah, seperti String. Saat membuat koleksi, cobalah membuat koleksi yang tidak dapat diubah atau diubah, seperti menggunakan metode seperti Arrays.asList() dan unmodifiedList() Koleksi. Dengan menghindari variabilitas kita dapat menulis fungsi murni - yaitu fungsi tanpa efek samping.
menghindari efek samping
Misalkan Anda menulis sepotong kode untuk mengambil harga suatu saham dari Internet dan menuliskannya ke variabel bersama. Jika kita memiliki banyak harga untuk diambil, kita harus melakukan operasi yang memakan waktu ini secara serial. Jika kita ingin memanfaatkan kekuatan multithreading, kita harus mengatasi kerumitan threading dan sinkronisasi untuk mencegah kondisi balapan. Hasil akhirnya adalah kinerja program ini sangat buruk, dan orang-orang lupa makan dan tidur untuk menjaga thread tersebut. Jika efek samping dihilangkan, kita dapat menghindari masalah ini sepenuhnya. Fungsi tanpa efek samping meningkatkan kekekalan dan tidak mengubah masukan apa pun atau apa pun dalam cakupannya. Fungsi semacam ini sangat mudah dibaca, memiliki sedikit kesalahan, dan mudah dioptimalkan. Karena tidak ada efek samping, tidak perlu khawatir dengan kondisi balapan atau modifikasi secara bersamaan. Tidak hanya itu, kita dapat dengan mudah menjalankan fungsi-fungsi tersebut secara paralel, yang akan kita bahas di halaman 145.
Lebih suka ekspresi
Pernyataan adalah hal yang menarik karena memaksa modifikasi. Ekspresi mendorong kekekalan dan komposisi fungsi. Misalnya, pertama-tama kita menggunakan pernyataan for untuk menghitung harga total setelah diskon. Kode seperti itu mengarah pada variabilitas dan kode verbose. Menggunakan versi peta dan metode penjumlahan yang lebih ekspresif dan deklaratif tidak hanya menghindari operasi modifikasi, namun juga memungkinkan fungsi dirangkai bersama. Saat menulis kode, Anda harus mencoba menggunakan ekspresi, bukan pernyataan. Ini membuat kodenya lebih sederhana dan mudah dipahami. Kode akan dieksekusi sesuai logika bisnis, sama seperti saat kami menjelaskan masalahnya. Versi ringkas tidak diragukan lagi lebih mudah untuk dimodifikasi jika persyaratan berubah.
Desain menggunakan fungsi tingkat tinggi
Java tidak menerapkan kekekalan seperti bahasa fungsional seperti Haskell, tetapi memungkinkan kita untuk mengubah variabel. Oleh karena itu, Java bukanlah dan tidak akan pernah menjadi bahasa pemrograman yang sepenuhnya berfungsi. Namun, kita dapat menggunakan fungsi tingkat tinggi untuk pemrograman fungsional di Java. Fungsi tingkat tinggi membawa penggunaan kembali ke tingkat berikutnya. Dengan fungsi tingkat tinggi, kita dapat dengan mudah menggunakan kembali kode matang yang kecil, terspesialisasi, dan sangat kohesif. Dalam OOP, kita terbiasa meneruskan objek ke metode, membuat objek baru dalam metode, dan kemudian mengembalikan objek tersebut. Fungsi tingkat tinggi melakukan hal yang sama terhadap fungsi seperti yang dilakukan metode terhadap objek. Dengan fungsi tingkat tinggi kita bisa.
1. Meneruskan fungsi ke fungsi
2. Buat fungsi baru di dalam fungsi tersebut
3. Mengembalikan fungsi di dalam fungsi
Kita telah melihat contoh meneruskan parameter dari suatu fungsi ke fungsi lain, dan nanti kita akan melihat contoh pembuatan dan pengembalian fungsi. Mari kita lihat contoh “meneruskan parameter ke suatu fungsi” lagi:
Copy kode kodenya sebagai berikut:
harga.stream()
.filter(harga -> harga.compareTo(BigDecimal.valueOf(20)) > 0) .map(harga -> harga.multiply(BigDecimal.valueOf(0.9)))
laporkan kesalahan • diskusikan
.reduce(BigDecimal.ZERO, BigDecimal::add);
Dalam kode ini, kita meneruskan fungsi price -> price.multiply(BigDecimal.valueOf(0.9)) ke fungsi map. Fungsi yang diteruskan dibuat ketika peta fungsi tingkat tinggi dipanggil. Secara umum, suatu fungsi memiliki isi fungsi, nama fungsi, daftar parameter, dan nilai kembalian. Fungsi yang dibuat dengan cepat ini memiliki daftar parameter diikuti oleh panah (->), dan kemudian isi fungsi pendek. Tipe parameter disimpulkan oleh kompiler Java, dan tipe kembaliannya juga implisit. Ini adalah fungsi anonim, tidak memiliki nama. Namun kami tidak menyebutnya fungsi anonim, kami menyebutnya ekspresi lambda. Meneruskan fungsi anonim sebagai parameter bukanlah hal baru di Java, kita sering melewatkan kelas dalam anonim sebelumnya. Bahkan jika kelas anonim hanya memiliki satu metode, kita masih harus melalui ritual pembuatan kelas dan membuat instance-nya. Dengan ekspresi lambda kita dapat menikmati sintaksis yang ringan. Tidak hanya itu, kita selalu terbiasa mengabstraksi beberapa konsep ke dalam berbagai objek, namun sekarang kita dapat mengabstraksi beberapa perilaku ke dalam ekspresi lambda. Pemrograman dengan gaya pengkodean ini masih memerlukan pemikiran. Kita harus mengubah pemikiran imperatif yang sudah mendarah daging menjadi pemikiran fungsional. Ini mungkin sedikit menyakitkan pada awalnya, tetapi Anda akan segera terbiasa dengannya, seiring Anda terus memperdalam, API yang tidak berfungsi tersebut secara bertahap akan tertinggal. Mari kita hentikan topik ini terlebih dahulu. Mari kita lihat bagaimana Java menangani ekspresi lambda. Kita dulu selalu meneruskan objek ke metode, sekarang kita bisa menyimpan fungsi dan menyebarkannya. Mari kita lihat rahasia di balik kemampuan Java dalam mengambil fungsi sebagai parameter.
Bagian 5: Menambahkan beberapa gula sintaksis
Hal ini juga dapat dicapai dengan menggunakan fungsi asli Java, tetapi ekspresi lambda menambahkan sedikit gula sintaksis, menghemat beberapa langkah dan membuat pekerjaan kita lebih sederhana. Kode yang ditulis dengan cara ini tidak hanya berkembang lebih cepat, tetapi juga mengekspresikan ide-ide kita dengan lebih baik. Banyak antarmuka yang kami gunakan di masa lalu hanya memiliki satu metode: Runnable, Callable, dll. Antarmuka ini dapat ditemukan di mana saja di perpustakaan JDK, dan di mana pun antarmuka tersebut digunakan, biasanya dapat dilakukan dengan suatu fungsi. Fungsi perpustakaan yang sebelumnya hanya memerlukan antarmuka metode tunggal kini dapat melewati fungsi ringan, berkat gula sintaksis yang disediakan oleh antarmuka fungsional. Antarmuka fungsional adalah antarmuka dengan hanya satu metode abstrak. Lihatlah antarmuka yang hanya memiliki satu metode, Runnable, Callable, dll., definisi ini berlaku untuk mereka. Ada lebih banyak antarmuka seperti itu di JDK8 - Fungsi, Predikat, Konsumen, Pemasok, dll. (halaman 157, Lampiran 1 memiliki daftar antarmuka yang lebih rinci). Antarmuka fungsional dapat memiliki beberapa metode statis dan metode default, yang diimplementasikan dalam antarmuka. Kita dapat menggunakan anotasi @FunctionalInterface untuk memberi anotasi pada antarmuka fungsional. Kompiler tidak menggunakan anotasi ini, tetapi dapat mengidentifikasi jenis antarmuka ini dengan lebih jelas. Tidak hanya itu, jika kita memberi anotasi antarmuka dengan anotasi ini, kompiler akan memverifikasi secara paksa apakah itu sesuai dengan aturan antarmuka fungsional. Jika suatu metode menerima antarmuka fungsional sebagai parameter, parameter yang dapat kita lewati termasuk:
1. Kelas batin anonim, cara tertua
2. Ekspresi Lambda, seperti yang kami lakukan di metode peta
3. Referensi ke metode atau konstruktor (kita akan membicarakannya nanti)
Jika parameter metode adalah antarmuka fungsional, kompiler akan dengan senang hati menerima ekspresi lambda atau referensi metode sebagai parameter. Jika kita meneruskan ekspresi lambda ke suatu metode, kompiler pertama -tama akan mengubah ekspresi menjadi instance dari antarmuka fungsional yang sesuai. Transformasi ini lebih dari sekadar menghasilkan kelas dalam. Metode instance yang dihasilkan secara sinkron ini sesuai dengan metode abstrak dari antarmuka fungsional parameter. Misalnya, metode MAP menerima fungsi antarmuka fungsional sebagai parameter. Saat memanggil metode peta, kompiler Java akan menghasilkannya secara serempak, seperti yang ditunjukkan pada gambar di bawah ini.
Parameter ekspresi lambda harus cocok dengan parameter metode abstrak antarmuka. Metode yang dihasilkan ini akan mengembalikan hasil ekspresi Lambda. Jika tipe pengembalian tidak secara langsung cocok dengan metode abstrak, metode ini akan mengonversi nilai pengembalian ke tipe yang sesuai. Kami sudah memiliki gambaran tentang bagaimana ekspresi Lambda diteruskan ke metode. Mari kita tinjau dengan cepat apa yang baru saja kita bicarakan, dan kemudian mulai eksplorasi ekspresi lambda kami.
Meringkaskan
Ini adalah area Java yang sama sekali baru. Melalui fungsi tingkat tinggi, kita sekarang dapat menulis kode gaya fungsional yang elegan dan fasih. Kode yang ditulis dengan cara ini ringkas dan mudah dimengerti, memiliki beberapa kesalahan, dan kondusif untuk pemeliharaan dan paralelisasi. Kompiler Java bekerja keajaibannya, dan di mana kita menerima parameter antarmuka fungsional, kita dapat lulus dalam ekspresi lambda atau referensi metode. Kita sekarang dapat memasuki dunia ekspresi lambda dan perpustakaan JDK yang diadaptasi agar mereka merasakan kesenangan dari mereka. Pada bab berikutnya, kita akan mulai dengan operasi set yang paling umum dalam pemrograman dan melepaskan kekuatan ekspresi lambda.