Optional adalah wadah yang mungkin memuat satu nilai atau kosong sama sekali, dan kelas ini dirancang untuk menggantikan pengembalian null pada metode yang pencariannya bisa gagal. Alih-alih memaksa pemanggil memeriksa null, metode yang mengembalikan Optional menyatakan kemungkinan kosong itu secara eksplisit pada tipe kembaliannya. Topik ini membahas cara membuat dan memeriksa Optional, merangkai nilainya dengan map dan flatMap, serta mengekstraksi isinya dengan aman sehingga NullPointerException tidak lagi muncul dari hasil pencarian.
Membuat dan memeriksa Optional#
Terdapat tiga pabrik pembuatan yang paling sering dipakai. Optional.of(nilai) membungkus nilai yang pasti tidak kosong dan menolak null dengan exception, Optional.empty() membuat kotak kosong yang sah, dan Optional.ofNullable(nilai) menerima keduanya dengan mengubah null menjadi kotak kosong. Pemeriksaan keberadaannya memakai isPresent() dan isEmpty(), sedangkan ekstraksi amannya memakai orElse yang memberi nilai cadangan bila kotaknya kosong.
import java.util.Optional;
public class DemoOptionalDasar {
public static void main(String[] args) {
Optional<String> judulTerisi = Optional.of("Peta Jalur Pendakian");
Optional<String> judulKosong = Optional.empty();
System.out.println("Terisi : " + judulTerisi.isPresent());
System.out.println("Kosong : " + judulKosong.isEmpty());
// orElse memberi nilai cadangan bila kotaknya kosong
String judul = judulKosong.orElse("(tanpa judul)");
System.out.println("Cadangan: " + judul);
// map mengubah isi tanpa pemeriksaan keberadaan secara manual
Optional<Integer> panjang = judulTerisi.map(String::length);
System.out.println("Panjang : " + panjang.orElse(0));
System.out.println("Panjang dari kotak kosong: "
+ judulKosong.map(String::length).orElse(0));
}
}Terisi : true
Kosong : true
Cadangan: (tanpa judul)
Panjang : 20
Panjang dari kotak kosong: 0Baris terakhir memperlihatkan keunggulan utamanya. Pemanggilan map pada kotak kosong tidak menyentuh fungsi pengubahnya sama sekali dan langsung menghasilkan kotak kosong, sehingga pola “periksa dulu, baru olah” yang biasa ditulis dengan if tidak diperlukan. Seluruh cabang kosong mengalir ke orElse di ujungnya, dan nilai cadangan itu menjadi hasil akhir yang bertipe Integer, bukan Optional<Integer> lagi.
Satu detail menarik menyertai orElse, yaitu argumennya selalu dihitung meskipun kotaknya terisi, karena Java mengevaluasi argumen pemanggilan metode lebih dahulu. Bila nilai cadangan mahal untuk dibuat, pasangannya orElseGet menerima Supplier dan hanya memanggilnya saat kotak memang kosong. Keduanya mengembalikan hasil yang sama, sehingga pilihannya semata soal biaya pembuatan nilai cadangan itu.
Merangkai nilai dengan map dan flatMap#
Perbedaan map dan flatMap baru terasa ketika fungsi pengubahnya sendiri mengembalikan Optional. map membungkus ulang hasilnya secara otomatis, sehingga fungsi yang sudah mengembalikan Optional justru menghasilkan kotak di dalam kotak, yaitu Optional<Optional<T>>. Untuk situasi itu, flatMap dipakai karena tidak membungkus ulang dan langsung meratakannya menjadi satu kotak.
Contoh klasiknya adalah pencarian berlapis, misalnya mencari anggota perpustakaan lalu memeriksa kartu anggotanya. Metode pencarian yang baik mengembalikan Optional<Anggota>, dan field kartu pada kelas Anggota memang dideklarasikan sebagai Optional<KartuAnggota> karena tidak semua anggota memiliki kartu. Rangkaian berikut menelusuri dua lapis kemungkinan kosong itu dalam satu ekspresi.
import java.util.Optional;
public class AlurOptional {
record KartuAnggota(String nomorSeri) {}
record Anggota(String nama, Optional<KartuAnggota> kartu) {}
static Optional<Anggota> cariAnggota(String nama) {
if (nama.equals("Dewi")) {
return Optional.of(new Anggota("Dewi",
Optional.of(new KartuAnggota("KA-021"))));
}
if (nama.equals("Budi")) {
return Optional.of(new Anggota("Budi", Optional.empty()));
}
return Optional.empty();
}
static String nomorKartu(Optional<Anggota> anggota) {
return anggota
.flatMap(a -> a.kartu())
.map(KartuAnggota::nomorSeri)
.orElse("tidak tersedia");
}
public static void main(String[] args) {
System.out.println("Dewi : " + nomorKartu(cariAnggota("Dewi")));
System.out.println("Budi : " + nomorKartu(cariAnggota("Budi")));
System.out.println("Rara : " + nomorKartu(cariAnggota("Rara")));
}
}Dewi : KA-021
Budi : tidak tersedia
Rara : tidak tersediaAlurnya digambarkan berikut ini, dan ketiga kasusnya mengalir ke satu pintu keluar yang sama.
Kasus Budi menunjukkan kegunaan flatMap secara nyata, karena a.kartu() sudah bertipe Optional<KartuAnggota> dan map hanya akan menggandakan pembungkusnya. Kasus Rara memperlihatkan bahwa kotak kosong di lapis pertama pun berhenti di jalur yang sama tanpa satu pun if. Rangkaian sepanjang ini tetap satu ekspresi yang bertipe String, dan tiap lapis kemungkinan kosongnya tertangani oleh struktur data itu sendiri.
Ekstraksi yang menolak diam-diam#
orElse menutup cabang kosong dengan nilai cadangan, tetapi ada kasus ketika kosong justru berarti kondisi abnormal yang tidak boleh diteruskan. Untuk itu tersedia orElseThrow yang melempar NoSuchElementException bila kotaknya kosong dan mengembalikan isinya bila terisi. Melempar exception yang jelas di titik pencarian lebih aman daripada membiarkan null berjalan jauh lalu meledak di tempat yang asalnya tidak berkaitan dengan pencarian itu.
import java.util.Optional;
public class DemoOrElseThrow {
public static void main(String[] args) {
Optional<String> tanpaNilai = Optional.empty();
try {
String nilai = tanpaNilai.orElseThrow();
System.out.println("Baris ini tidak pernah tercapai: " + nilai);
} catch (java.util.NoSuchElementException e) {
System.out.println("Exception tertangkap: " + e.getMessage());
}
}
}Exception tertangkap: No value presentException yang dilempar orElseThrow menyatakan kegagalan pencarian dengan jelas, dan penangkapannya di contoh ini hanya untuk memperlihatkan pesannya. Pemakaian try/catch seperti di atas bukan pola sehari-hari, karena kegagalan Optional sebaiknya ditangani lewat orElse, orElseGet, atau cabang lain yang disediakan kelas ini. Dengan disiplin itu, NullPointerException akibat hasil pencarian yang ternyata kosong tidak lagi punya celah untuk muncul.
Kesalahan umum#
- Memakai
Optional.ofpada nilai yang mungkinnull. BentukOptional.of(nilai)melemparNullPointerExceptionsaat itu juga bila nilai yang dikirim kosong, sehingga persoalan hanya berpindah tempat. GunakanOptional.ofNullablebila keberadaan nilai memang tidak pasti. - Membungkus ulang hasil fungsi yang sudah mengembalikan
Optionaldenganmap. Hasilnya kotak di dalam kotak yang sulit diekstraksi. PakaiflatMapuntuk fungsi yang sudah mengembalikanOptional. - Memanggil
get()tanpa pemeriksaan.get()pada kotak kosong melemparNoSuchElementExceptiondan membawa kebiasaan lama berupa pemeriksaanisPresent()sebelumget(). PilihorElse,orElseGet, atauorElseThrowyang menyatakan maksudnya secara langsung. - Menyimpan field bertipe
Optionallalu membandingkannya atau mengirimnya sebagai parameter.Optionalbukan dirancang untuk kedua peran itu dan membuat kontrak kelas menjadi kabur. Simpan tipe langsungnya di field, dan kembalikanOptionalhanya dari metode pencarian.
Ringkasan#
Optionalmembungkus nilai yang mungkin kosong dengan pabrikof,empty, danofNullable, serta memeriksanya lewatisPresentdanisEmpty.mapmengubah isi kotak dan merawat kotak kosong secara otomatis, sedangkanflatMapmerangkai fungsi yang sudah mengembalikanOptionaltanpa membungkus ulang.- Ekstraksi amannya memakai
orElseatauorElseGetuntuk nilai cadangan, danorElseThrowbila kekosongan justru abnormal. Optionalyang dipakai disiplin sebagai tipe kembalian metode pencarian menghapusNullPointerExceptiondari jalur hasil pencarian.
Referensi#
- Optional (Java SE 21 & JDK 21) — Oracle — daftar lengkap metode Optional beserta kontrak masing-masing pada dokumentasi resmi JDK 21.
- The Java Tutorials: Aggregate Operations — Oracle — penjelasan peran Optional sebagai tipe kembalian operasi agregat pada tutorial resmi Java.