Banyak yang telah mendengar bahasa fungsional seperti Haskell dan Clojure. Tetapi ada bahasa seperti Scala, misalnya. Ini menggabungkan OOP dan pendekatan fungsional. Bagaimana dengan Jawa tua yang baik? Apakah mungkin untuk menulis program dengan gaya fungsional di atasnya dan seberapa besar kerugiannya? Ya, ada Java 8 dan lambdas dengan stream. Ini adalah langkah besar untuk bahasa, tetapi itu masih belum cukup. Apakah mungkin untuk menemukan sesuatu dalam situasi ini? Ternyata iya.
Untuk memulai, mari kita coba menentukan apa arti penulisan kode dengan gaya fungsional. Pertama, kita harus beroperasi bukan dengan variabel dan manipulasi dengannya, tetapi dengan rantai perhitungan. Intinya, urutan fungsi. Selain itu, kita harus memiliki struktur data khusus. Misalnya, koleksi java standar tidak cocok. Itu akan segera menjadi jelas mengapa.
Mari kita pertimbangkan struktur fungsional secara lebih detail. Setiap struktur seperti itu harus memenuhi setidaknya dua syarat:
- kekal - struktur harus kekal. Ini berarti bahwa kita memperbaiki keadaan objek pada tahap penciptaan dan membiarkannya seperti itu sampai akhir keberadaannya. Contoh nyata pelanggaran kondisi: standar ArrayList.
- persisten - struktur harus disimpan dalam memori selama mungkin. Jika kita membuat beberapa objek, maka alih-alih membuat yang baru dengan status yang sama, kita harus menggunakan yang siap. Secara lebih formal, struktur tersebut mempertahankan semua status sebelumnya setelah modifikasi. Referensi untuk kondisi ini harus tetap beroperasi penuh.
Jelas, kita membutuhkan semacam solusi pihak ketiga. Dan ada solusi seperti itu: Perpustakaan
VAVR . Hari ini adalah perpustakaan
Java paling populer untuk bekerja dalam gaya fungsional. Selanjutnya, saya akan menjelaskan fitur-fitur utama perpustakaan. Banyak, tetapi tidak berarti semua, contoh dan deskripsi diambil dari dokumentasi resmi.
Struktur data utama perpustakaan vavr
Tuple
Salah satu struktur data fungsional yang paling dasar dan sederhana adalah tupel. Sebuah tuple adalah seperangkat panjang tetap yang dipesan. Tidak seperti daftar, tuple dapat berisi data jenis apa pun.
Tuple tuple = Tuple.of(1, "blablabla", .0, 42L);
Mendapatkan item yang diinginkan datang dari memanggil bidang dengan nomor item di tuple.
((Tuple4) tuple)._1
Harap dicatat: pengindeksan tuple dimulai pada 1! Selain itu, untuk mendapatkan elemen yang diinginkan, kita harus mengubah objek kita ke tipe yang diinginkan dengan serangkaian metode yang sesuai. Dalam contoh di atas, kami menggunakan tupel dari 4 elemen, yang berarti konversi harus dari tipe
Tuple4 . Bahkan, tidak ada yang menghentikan kita dari membuat jenis yang tepat.
Tuple4 tuple = Tuple.of(1, "blablabla", .0, 42L);
3 koleksi vavr teratas
Daftar
Membuat daftar dengan vavr sangat sederhana. Bahkan lebih mudah daripada tanpa
vavr .
List.of(1, 2, 3)
Apa yang bisa kita lakukan dengan daftar seperti itu? Pertama, kita bisa mengubahnya menjadi daftar
java standar.
final boolean containThree = List.of(1, 2, 3) .asJava() .stream() .anyMatch(x -> x == 3);
Tetapi pada kenyataannya, ini tidak terlalu diperlukan, karena bisa kita lakukan, misalnya, seperti ini:
final boolean containThree = List.of(1, 2, 3) .find(x -> x == 1) .isDefined();
Secara umum,
daftar pustaka
vavr standar memiliki banyak metode yang berguna. Misalnya, ada fungsi konvolusi yang cukup kuat yang memungkinkan Anda untuk menggabungkan daftar nilai dengan beberapa aturan dan elemen netral.
Satu poin penting harus diperhatikan di sini. Kami memiliki struktur data fungsional, yang berarti bahwa kami tidak dapat mengubah statusnya. Bagaimana daftar kami diimplementasikan? Array tidak cocok untuk kita.
Daftar Tertaut sebagai daftar defaultMari kita membuat daftar yang hanya ditautkan dengan benda yang tidak dapat diubah. Akan terlihat seperti ini:

Contoh kode List list = List.of(1, 2, 3);
Setiap elemen dari daftar memiliki dua metode utama: memperoleh elemen kepala (kepala) dan yang lainnya (ekor).
Sekarang, jika kita ingin mengubah elemen pertama dalam daftar (dari 1 ke 0), maka kita perlu membuat daftar baru dengan menggunakan kembali bagian jadi.

Contoh kode final List tailList = list.tail();
Dan itu saja! Karena objek kami di lembar kerja tidak dapat diubah, kami mendapatkan koleksi yang aman dan dapat digunakan kembali. Elemen daftar kami dapat diterapkan di mana saja di aplikasi dan itu benar-benar aman!
Antrian
Struktur data lain yang sangat berguna adalah antrian. Bagaimana cara membuat antrian untuk membangun program yang efektif dan andal dalam gaya fungsional? Sebagai contoh, kita dapat mengambil struktur data yang sudah kita ketahui: dua daftar dan satu tupel.

Contoh kode Queue<Integer> queue = Queue.of(1, 2, 3) .enqueue(4) .enqueue(5);
Saat yang pertama berakhir, kami memperluas yang kedua dan menggunakannya untuk membaca.


Penting untuk diingat bahwa antrian harus tidak berubah, seperti semua struktur lainnya. Tapi apa gunanya antrian yang tidak berubah? Padahal, ada trik. Sebagai nilai antrian yang diterima, kami mendapatkan dua elemen. Pertama: elemen antrian yang diinginkan, kedua: apa yang terjadi pada antrian tanpa elemen ini.
System.out.println(queue);
Streaming
Struktur data penting berikutnya adalah stream. Aliran adalah aliran pelaksanaan beberapa tindakan pada set nilai tertentu, seringkali abstrak.
Seseorang mungkin mengatakan bahwa
Java 8 sudah memiliki aliran penuh dan kami tidak membutuhkan yang baru sama sekali. Benarkah begitu?
Untuk memulai, mari pastikan
java stream bukan struktur data fungsional. Periksa struktur untuk mutabilitas. Untuk melakukan ini, buat aliran kecil seperti itu:
IntStream standardStream = IntStream.range(1, 10);
Kami akan memilah-milah semua elemen di aliran:
standardStream.forEach(System.out::print);
Sebagai tanggapan, kami mendapatkan output ke konsol:
123456789 . Mari kita ulangi operasi brute force:
standardStream.forEach(System.out::print);
Ups, terjadi kesalahan berikut:
java.lang.IllegalStateException: stream has already been operated upon or closed
Faktanya adalah stream standar hanya semacam abstraksi atas iterator. Meskipun aliran di luar tampak sangat independen dan kuat, minus iterator belum hilang.
Misalnya, definisi aliran tidak mengatakan apa pun tentang membatasi jumlah elemen. Sayangnya, itu ada di iterator, yang berarti di stream standar.
Untungnya, pustaka vavr memecahkan masalah ini. Pastikan ini:
Stream stream = Stream.range(1, 10); stream.forEach(System.out::print); stream.forEach(System.out::print);
Sebagai tanggapan, kami mendapatkan
123456789123456789 . Yang berarti operasi pertama tidak "merusak" aliran kami.
Mari kita coba membuat aliran tanpa akhir:
Stream infiniteStream = Stream.from (1);
System.out.println (infiniteStream); // Streaming (1 ,?)
Harap dicatat: saat mencetak objek, kita tidak mendapatkan struktur yang tak terbatas, tetapi elemen pertama dan tanda tanya. Faktanya adalah bahwa setiap elemen berikutnya dalam aliran dihasilkan dengan cepat. Pendekatan ini disebut inisialisasi malas. Dialah yang memungkinkan Anda untuk bekerja dengan aman dengan struktur seperti itu.
Jika Anda belum pernah bekerja dengan struktur data tanpa batas, maka kemungkinan besar Anda berpikir: mengapa ini perlu? Tetapi mereka bisa sangat nyaman. Kami menulis aliran yang mengembalikan jumlah angka ganjil yang sewenang-wenang, mengubahnya menjadi string dan menambahkan spasi:
Stream oddNumbers = Stream .from(1, 2)
Sangat sederhana.
Struktur umum koleksi
Setelah kita membahas struktur dasar, saatnya untuk melihat arsitektur umum koleksi fungsional
vavr :

Setiap elemen struktur dapat digunakan sesuai keinginan:
StringBuilder builder = new StringBuilder(); for (String word : List.of("one", "two", "tree")) { if (builder.length() > 0) { builder.append(", "); } builder.append(word); } System.out.println(builder.toString());
Tetapi Anda harus berpikir dua kali dan melihat dermaga sebelum menggunakannya. Perpustakaan memungkinkan Anda untuk mempermudah hal-hal yang sudah dikenal.
System.out.println(List.of("one", "two", "tree").mkString(", "));
Bekerja dengan fungsi
Perpustakaan memiliki sejumlah fungsi (8 buah) dan metode yang berguna untuk bekerja dengannya. Mereka adalah antarmuka fungsional biasa dengan banyak metode menarik. Nama fungsi tergantung pada jumlah argumen yang diterima (dari 0 hingga 8). Misalnya,
Function0 tidak mengambil argumen,
Function1 mengambil satu argumen,
Function2 mengambil dua argumen, dll.
Function2<String, String, String> combineName = (lastName, firstName) -> firstName + " " + lastName; System.out.println(combineName.apply("Griffin", "Peter"));
Dalam fungsi pustaka vavr, kita dapat melakukan banyak hal keren. Dalam hal fungsionalitas, mereka unggul jauh dari Fungsi standar, BiFungsi, dll. Misalnya, kari. Kari adalah pembangunan fungsi di bagian-bagian. Mari kita lihat sebuah contoh:
Seperti yang Anda lihat, cukup ringkas. Metode
kari sangat sederhana, tetapi bisa sangat berguna.
Implementasi metode kari @Override default Function1<T1, Function1<T2, R>> curried() { return t1 -> t2 -> apply(t1, t2); }
Ada banyak metode yang lebih berguna dalam Kumpulan
fungsi . Misalnya, Anda dapat menembolok hasil pengembalian suatu fungsi:
Function0<Double> hashCache = Function0.of(Math::random).memoized(); double randomValue1 = hashCache.apply(); double randomValue2 = hashCache.apply(); System.out.println(randomValue1 == randomValue2);
Berjuang melawan pengecualian
Seperti yang kami katakan sebelumnya, proses pemrograman harus aman. Untuk melakukan ini, perlu untuk menghindari berbagai efek asing. Pengecualian adalah generator eksplisit mereka.
Anda bisa menggunakan kelas
Coba untuk menangani pengecualian dengan aman dalam gaya fungsional. Padahal, ini adalah
monad yang khas. Untuk mempelajari teori untuk digunakan tidak perlu. Lihat saja contoh sederhana:
Try.of(() -> 4 / 0) .onFailure(System.out::println) .onSuccess(System.out::println);
Seperti yang dapat Anda lihat dari contoh, semuanya cukup sederhana. Kami hanya menggantung acara pada kesalahan potensial dan tidak membawanya melampaui batas perhitungan.
Pencocokan pola
Seringkali muncul situasi di mana kita perlu memeriksa nilai variabel dan memodelkan perilaku program tergantung pada hasilnya. Hanya dalam situasi seperti itu, mesin pencari template yang bagus datang untuk menyelamatkan. Anda tidak lagi harus menulis banyak
jika lain , cukup konfigurasikan semua logika di satu tempat.
import static io.vavr.API.*; import static io.vavr.Predicates.*; public class PatternMatchingDemo { public static void main(String[] args) { String s = Match(1993).of( Case($(42), () -> "one"), Case($(anyOf(isIn(1990, 1991, 1992), is(1993))), "two"), Case($(), "?") ); System.out.println(s);
Harap dicatat Kasus dikapitalisasi, sebagai kasing adalah kata kunci dan sudah diambil.
Kesimpulan
Menurut pendapat saya, perpustakaan itu sangat keren, tetapi layak menggunakannya dengan sangat hati-hati. Dia bisa melakukan yang terbaik dalam pengembangan
berbasis acara . Namun, penggunaannya yang berlebihan dan tidak dipikirkan dalam pemrograman imperatif standar berdasarkan pada thread pool dapat membawa banyak sakit kepala. Selain itu, sering dalam proyek kami, kami menggunakan Spring dan Hibernate, yang tidak selalu siap untuk aplikasi seperti itu. Sebelum mengimpor perpustakaan ke proyek Anda, Anda perlu pemahaman yang jelas tentang bagaimana dan mengapa itu akan digunakan. Apa yang akan saya bicarakan di salah satu artikel saya berikutnya.