Mengapa Anda Harus Upgrade ke Java 8?
Diterbitkan: 2022-03-11Versi terbaru dari platform Java, Java 8, dirilis lebih dari setahun yang lalu. Banyak perusahaan dan pengembang masih bekerja dengan versi sebelumnya, yang dapat dimengerti, karena ada banyak masalah dengan migrasi dari satu versi platform ke versi lainnya. Meski begitu, banyak pengembang masih memulai aplikasi baru dengan Java versi lama. Ada sedikit alasan bagus untuk melakukan ini, karena Java 8 telah membawa beberapa peningkatan penting pada bahasa tersebut.
Ada banyak fitur baru di Java 8. Saya akan menunjukkan beberapa fitur yang paling berguna dan menarik:
- ekspresi lambda
- Streaming API untuk bekerja dengan Koleksi
- Rantai tugas asinkron dengan
CompletableFuture
- API Waktu baru
Ekspresi Lambda
Lambda adalah blok kode yang dapat direferensikan dan diteruskan ke bagian kode lain untuk eksekusi di masa mendatang satu kali atau lebih. Misalnya, fungsi anonim dalam bahasa lain adalah lambda. Seperti fungsi, lambda dapat diteruskan argumen pada saat eksekusi, memodifikasi hasilnya. Java 8 memperkenalkan ekspresi lambda , yang menawarkan sintaks sederhana untuk membuat dan menggunakan lambda.
Mari kita lihat contoh bagaimana ini dapat meningkatkan kode kita. Di sini kita memiliki komparator sederhana yang membandingkan dua nilai Integer
dengan modulo 2:
class BinaryComparator implements Comparator<Integer>{ @Override public int compare(Integer i1, Integer i2) { return i1 % 2 - i2 % 2; } }
Sebuah instance dari kelas ini dapat dipanggil, di masa depan, dalam kode di mana komparator ini diperlukan, seperti ini:
... List<Integer> list = ...; Comparator<Integer> comparator = new BinaryComparator(); Collections.sort(list, comparator); ...
Sintaks lambda baru memungkinkan kita melakukan ini dengan lebih sederhana. Berikut adalah ekspresi lambda sederhana yang melakukan hal yang sama seperti metode compare
dari BinaryComparator
:
(Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
Struktur memiliki banyak kesamaan dengan fungsi. Dalam tanda kurung, kami menyiapkan daftar argumen. Sintaks ->
menunjukkan bahwa ini adalah lambda. Dan di bagian kanan ekspresi ini, kami mengatur perilaku lambda kami.
Sekarang kita dapat meningkatkan contoh sebelumnya:
... List<Integer> list = ...; Collections.sort(list, (Integer i1, Integer i2) -> i1 % 2 - i2 % 2); ...
Kami dapat mendefinisikan variabel dengan objek ini. Mari kita lihat bagaimana tampilannya:
Comparator<Integer> comparator = (Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
Sekarang kita dapat menggunakan kembali fungsi ini, seperti ini:
... List<Integer> list1 = ...; List<Integer> list2 = ...; Collections.sort(list1, comparator); Collections.sort(list2, comparator); ...
Perhatikan bahwa dalam contoh ini, lambda diteruskan ke metode sort()
dengan cara yang sama seperti instance BinaryComparator
diteruskan pada contoh sebelumnya. Bagaimana JVM tahu untuk menafsirkan lambda dengan benar?
Untuk mengizinkan fungsi mengambil lambdas sebagai argumen, Java 8 memperkenalkan konsep baru: antarmuka fungsional . Antarmuka fungsional adalah antarmuka yang hanya memiliki satu metode abstrak. Faktanya, Java 8 memperlakukan ekspresi lambda sebagai implementasi khusus dari antarmuka fungsional. Ini berarti, untuk menerima lambda sebagai argumen metode, tipe yang dideklarasikan argumen itu hanya perlu antarmuka fungsional.
Saat mendeklarasikan antarmuka fungsional, kami dapat menambahkan notasi @FunctionalInterface
untuk menunjukkan kepada pengembang apa itu:
@FunctionalInterface private interface DTOSender { void send(String accountId, DTO dto); } void sendDTO(BisnessModel object, DTOSender dtoSender) { //some logic for sending... ... dtoSender.send(id, dto); ... }
Sekarang, kita dapat memanggil metode sendDTO
, meneruskan lambda yang berbeda untuk mencapai perilaku yang berbeda, seperti ini:
sendDTO(object, ((accountId, dto) -> sendToAndroid(accountId, dto))); sendDTO(object, ((accountId, dto) -> sendToIos(accountId, dto)));
Referensi Metode
Argumen Lambda memungkinkan kita untuk mengubah perilaku suatu fungsi atau metode. Seperti yang dapat kita lihat pada contoh terakhir, terkadang lambda hanya berfungsi untuk memanggil metode lain ( sendToAndroid
atau sendToIos
). Untuk kasus khusus ini, Java 8 memperkenalkan singkatan praktis: referensi metode . Sintaks yang disingkat ini mewakili lambda yang memanggil metode, dan memiliki bentuk objectName::methodName
. Ini memungkinkan kita untuk membuat contoh sebelumnya lebih ringkas dan mudah dibaca:
sendDTO(object, this::sendToAndroid); sendDTO(object, this::sendToIos);
Dalam hal ini, metode sendToAndroid
dan sendToIos
diimplementasikan di kelas this
. Kami juga dapat mereferensikan metode objek atau kelas lain.
Aliran API
Java 8 menghadirkan kemampuan baru untuk bekerja dengan Collections
, dalam bentuk Stream API baru. Fungsionalitas baru ini disediakan oleh paket java.util.stream
, dan ditujukan untuk memungkinkan pendekatan yang lebih fungsional untuk pemrograman dengan koleksi. Seperti yang akan kita lihat, ini sebagian besar dimungkinkan berkat sintaks lambda baru yang baru saja kita diskusikan.
Stream API menawarkan pemfilteran, penghitungan, dan pemetaan koleksi yang mudah, serta berbagai cara untuk mendapatkan potongan dan subkumpulan informasi darinya. Berkat sintaks gaya fungsional, Stream API memungkinkan kode yang lebih pendek dan lebih elegan untuk bekerja dengan koleksi.
Mari kita mulai dengan contoh singkat. Kami akan menggunakan model data ini dalam semua contoh:
class Author { String name; int countOfBooks; } class Book { String name; int year; Author author; }
Mari kita bayangkan bahwa kita perlu mencetak semua penulis dalam kumpulan books
yang menulis buku setelah tahun 2005. Bagaimana kita melakukannya di Java 7?
for (Book book : books) { if (book.author != null && book.year > 2005){ System.out.println(book.author.name); } }
Dan bagaimana kita melakukannya di Java 8?
books.stream() .filter(book -> book.year > 2005) // filter out books published in or before 2005 .map(Book::getAuthor) // get the list of authors for the remaining books .filter(Objects::nonNull) // remove null authors from the list .map(Author::getName) // get the list of names for the remaining authors .forEach(System.out::println); // print the value of each remaining element
Itu hanya satu ekspresi! Memanggil metode stream()
pada Collection
mana pun akan mengembalikan objek Stream
yang merangkum semua elemen koleksi itu. Ini dapat dimanipulasi dengan pengubah berbeda dari Stream API, seperti filter()
dan map()
. Setiap pengubah mengembalikan objek Stream
baru dengan hasil modifikasi, yang dapat dimanipulasi lebih lanjut. Metode .forEach()
memungkinkan kita melakukan beberapa tindakan untuk setiap instance dari aliran yang dihasilkan.
Contoh ini juga menunjukkan hubungan erat antara pemrograman fungsional dan ekspresi lambda. Perhatikan bahwa argumen yang diteruskan ke setiap metode dalam aliran adalah lambda khusus, atau referensi metode. Secara teknis, setiap pengubah dapat menerima antarmuka fungsional apa pun, seperti yang dijelaskan di bagian sebelumnya.
Stream API membantu pengembang melihat koleksi Java dari sudut pandang baru. Bayangkan sekarang kita perlu mendapatkan Map
bahasa yang tersedia di setiap negara. Bagaimana ini diterapkan di Java 7?
Map<String, Set<String>> countryToSetOfLanguages = new HashMap<>(); for (Locale locale : Locale.getAvailableLocales()){ String country = locale.getDisplayCountry(); if (!countryToSetOfLanguages.containsKey(country)){ countryToSetOfLanguages.put(country, new HashSet<>()); } countryToSetOfLanguages.get(country).add(locale.getDisplayLanguage()); }
Di Java 8, semuanya sedikit lebih rapi:
import java.util.stream.*; import static java.util.stream.Collectors.*; ... Map<String, Set<String>> countryToSetOfLanguages = Stream.of(Locale.getAvailableLocales()) .collect(groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())));
Metode collect()
memungkinkan kita untuk mengumpulkan hasil aliran dengan cara yang berbeda. Di sini, kita dapat melihat bahwa itu pertama-tama mengelompokkan berdasarkan negara, dan kemudian memetakan setiap kelompok berdasarkan bahasa. ( groupingBy()
dan toSet()
keduanya adalah metode statis dari kelas Collectors
.)

Ada banyak kemampuan lain dari Stream API. Dokumentasi lengkap dapat ditemukan di sini. Saya sarankan membaca lebih lanjut untuk mendapatkan pemahaman yang lebih dalam tentang semua alat canggih yang ditawarkan paket ini.
Rantai Tugas Asinkron dengan CompletableFuture
Dalam paket java.util.concurrent
Java 7, ada antarmuka Future<T>
, yang memungkinkan kita mendapatkan status atau hasil dari beberapa tugas asinkron di masa mendatang. Untuk menggunakan fungsi ini, kita harus:
- Buat
ExecutorService
, yang mengelola eksekusi tugas asinkron, dan dapat menghasilkan objekFuture
untuk melacak kemajuannya. - Buat tugas
Runnable
secara asinkron. - Jalankan tugas di
ExecutorService
, yang akan memberikanFuture
yang memberikan akses ke status atau hasil.
Untuk memanfaatkan hasil tugas asinkron, perlu untuk memantau kemajuannya dari luar, menggunakan metode antarmuka Future
, dan ketika sudah siap, secara eksplisit mengambil hasil dan melakukan tindakan lebih lanjut dengan mereka. Ini bisa agak rumit untuk diterapkan tanpa kesalahan, terutama dalam aplikasi dengan sejumlah besar tugas bersamaan.
Di Java 8, bagaimanapun, konsep Future
diambil lebih jauh, dengan antarmuka CompletableFuture<T>
, yang memungkinkan pembuatan dan eksekusi rantai tugas asinkron. Ini adalah mekanisme yang kuat untuk membuat aplikasi asinkron di Java 8, karena memungkinkan kita untuk secara otomatis memproses hasil setiap tugas setelah selesai.
Mari kita lihat contohnya:
import java.util.concurrent.CompletableFuture; ... CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> blockingReadPage()) .thenApply(this::getLinks) .thenAccept(System.out::println);
Metode CompletableFuture.supplyAsync
membuat tugas asinkron baru di Executor
default (biasanya ForkJoinPool
). Ketika tugas selesai, hasilnya akan secara otomatis diberikan sebagai argumen ke fungsi this::getLinks
, yang juga dijalankan dalam tugas asinkron baru. Terakhir, hasil tahap kedua ini secara otomatis dicetak ke System.out
. thenApply()
dan thenAccept()
hanyalah dua dari beberapa metode berguna yang tersedia untuk membantu Anda membangun tugas bersamaan tanpa menggunakan Executors
secara manual.
CompletableFuture
memudahkan pengelolaan pengurutan operasi asinkron yang kompleks. Katakanlah kita perlu membuat operasi matematika multi-langkah dengan tiga tugas. Tugas 1 dan tugas 2 menggunakan algoritme yang berbeda untuk menemukan hasil untuk langkah pertama, dan kita tahu bahwa hanya satu dari mereka yang akan bekerja sementara yang lain akan gagal. Namun, mana yang berfungsi tergantung pada data input, yang tidak kita ketahui sebelumnya. Hasil dari tugas ini harus dijumlahkan dengan hasil tugas 3 . Jadi, kita perlu mencari hasil dari tugas 1 atau tugas 2 , dan hasil dari tugas 3 . Untuk mencapai ini, kita dapat menulis sesuatu seperti ini:
import static java.util.concurrent.CompletableFuture.*; ... Supplier<Integer> task1 = (...) -> { ... // some complex calculation return 1; // example result }; Supplier<Integer> task2 = (...) -> { ... // some complex calculation throw new RuntimeException(); // example exception }; Supplier<Integer> task3 = (...) -> { ... // some complex calculation return 3; // example result }; supplyAsync(task1) // run task1 .applyToEither( // use whichever result is ready first, result of task1 or supplyAsync(task2), // result of task2 (Integer i) -> i) // return result as-is .thenCombine( // combine result supplyAsync(task3), // with result of task3 Integer::sum) // using summation .thenAccept(System.out::println); // print final result after execution
Jika kita memeriksa bagaimana Java 8 menangani ini, kita akan melihat bahwa ketiga tugas akan dijalankan pada waktu yang sama, secara asinkron. Meskipun tugas 2 gagal dengan pengecualian, hasil akhir akan berhasil dihitung dan dicetak.
CompletableFuture
membuatnya lebih mudah untuk membangun tugas asinkron dengan beberapa tahap, dan memberi kita antarmuka yang mudah untuk menentukan dengan tepat tindakan apa yang harus diambil pada penyelesaian setiap tahap.
API Tanggal dan Waktu Java
Seperti yang dinyatakan oleh pengakuan Java sendiri:
Sebelum rilis Java SE 8, mekanisme tanggal dan waktu Java disediakan oleh kelas
java.util.Date
,java.util.Calendar
, danjava.util.TimeZone
, serta subkelasnya, sepertijava.util.GregorianCalendar
. Kelas-kelas ini memiliki beberapa kelemahan, termasuk
- Kelas Kalender tidak aman untuk diketik.
- Karena kelas dapat berubah, mereka tidak dapat digunakan dalam aplikasi multithread.
- Bug dalam kode aplikasi biasa terjadi karena penomoran bulan yang tidak biasa dan kurangnya keamanan jenis. ”
Java 8 akhirnya memecahkan masalah lama ini, dengan paket java.time
baru, yang berisi kelas untuk bekerja dengan tanggal dan waktu. Semuanya tidak dapat diubah dan memiliki API yang mirip dengan kerangka kerja populer Joda-Time, yang digunakan hampir semua pengembang Java dalam aplikasi mereka alih-alih Date
, Calendar
, dan TimeZone
.
Berikut adalah beberapa kelas yang berguna dalam paket ini:
-
Clock
- Jam untuk memberi tahu waktu saat ini, termasuk instan, tanggal, dan waktu saat ini dengan zona waktu. -
Duration
, danPeriod
- Jumlah waktu.Duration
menggunakan nilai berbasis waktu seperti “76,8 detik, danPeriod
, berbasis tanggal, seperti “4 tahun, 6 bulan, dan 12 hari”. -
Instant
- Titik waktu seketika, dalam beberapa format. -
LocalDate
,LocalDateTime
,LocalTime
,Year
,YearMonth
- Tanggal, waktu, tahun, bulan, atau kombinasinya, tanpa zona waktu dalam sistem kalender ISO-8601. -
OffsetDateTime
,OffsetTime
- Tanggal-waktu dengan offset dari UTC/Greenwich dalam sistem kalender ISO-8601, seperti "2015-08-29T14:15:30+01:00." -
ZonedDateTime
- Tanggal-waktu dengan zona waktu terkait dalam sistem kalender ISO-8601, seperti “1986-08-29T10:15:30+01:00 Eropa/Paris.”
Terkadang, kita perlu menemukan beberapa tanggal relatif seperti “Selasa pertama setiap bulan”. Untuk kasus ini java.time
menyediakan kelas khusus TemporalAdjuster
. Kelas TemporalAdjuster
berisi satu set adjuster standar, tersedia sebagai metode statis. Ini memungkinkan kita untuk:
- Temukan hari pertama atau terakhir dalam sebulan.
- Temukan hari pertama atau terakhir bulan berikutnya atau sebelumnya.
- Temukan hari pertama atau terakhir tahun ini.
- Temukan hari pertama atau terakhir tahun berikutnya atau sebelumnya.
- Temukan hari pertama atau terakhir dalam seminggu dalam sebulan, seperti "Rabu pertama di bulan Juni".
- Temukan hari berikutnya atau sebelumnya, seperti "Kamis depan".
Berikut adalah contoh singkat cara mendapatkan hari Selasa pertama setiap bulan:
LocalDate getFirstTuesday(int year, int month) { return LocalDate.of(year, month, 1) .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)); }
Ringkasan Java 8
Seperti yang bisa kita lihat, Java 8 adalah rilis penting dari platform Java. Ada banyak perubahan bahasa, terutama dengan diperkenalkannya lambdas, yang merupakan langkah untuk membawa kemampuan pemrograman yang lebih fungsional ke dalam Java. Stream API adalah contoh bagus bagaimana lambdas dapat mengubah cara kita bekerja dengan alat Java standar yang sudah biasa kita gunakan.
Selain itu, Java 8 menghadirkan beberapa fitur baru untuk bekerja dengan pemrograman asinkron dan perombakan alat tanggal dan waktu yang sangat dibutuhkan.
Bersama-sama, perubahan ini merupakan langkah maju yang besar untuk bahasa Java, membuat pengembangan Java lebih menarik dan lebih efisien.