Operator null-aware

Akses aman ?., nilai bawaan ??, dan cascade null-aware ?..

Daftar isi

Nilai null tidak boleh diakses secara langsung, tetapi program tetap harus menangani kemungkinan bahwa nilai yang dibutuhkan tidak ada. Operator null-aware menyediakan notasi singkat untuk keperluan itu, yaitu akses anggota yang aman, nilai bawaan ketika nilainya null, dan cascade yang seluruh rangkaiannya dilewati ketika objek targetnya null. Topik ini membahas ketiga operator tersebut beserta keluaran yang sebenarnya dihasilkan pada kasus pencarian data dan penulisan catatan.

Akses aman dengan operator ?.#

Operator ?. menjalankan akses anggota hanya ketika nilai di sebelah kirinya tidak null. Apabila nilainya null, keseluruhan ekspresi langsung bernilai null tanpa mengeksekusi bagian di sebelah kanan. Bentuk ini lazim disebut akses aman karena kegagalan yang biasanya berupa error saat berjalan diubah menjadi nilai null yang dapat ditangani dengan tenang.

Contoh berikut mencari nama antrean pada peta yang hanya memuat dua entri, sehingga pencarian nomor 13 menghasilkan null. Kedua hasil pencarian disimpan pada variabel bertipe String? sebelum dibaca. Perhatikan perbedaan keluaran antara ekspresi yang menggunakan ?. dan perilaku variabel nullable secara umum.

dart akses-aman.dart
String? cariNama(int nomorAntrean) {
  final daftar = {11: "Ali", 12: "Budi"};
  return daftar[nomorAntrean];
}

void main() {
  final nama11 = cariNama(11);
  final nama13 = cariNama(13);

  print(nama11);
  print(nama13);
  print(nama11?.length);
  print(nama13?.length);
  print(nama11?.toUpperCase());
}
bash
# menjalankan program dari folder topik
dart run akses-aman.dart
text Keluaran
Ali
null
3
null
ALI

Variabel nama11 dan nama13 sama-sama bertipe String? karena fungsi cariNama dapat mengembalikan null. Ekspresi nama11?.length bernilai 3 karena nama11 berisi teks, sedangkan nama13?.length bernilai null tanpa menjalankan akses apa pun. Panggilan metode mengikuti aturan yang sama, sehingga nama11?.toUpperCase() menghasilkan ALI, dan panggilan serupa pada nilai null tidak pernah dieksekusi. Tipe hasil ekspresi yang menggunakan ?. turut berubah, sehingga nama13?.length bertipe int? dan dapat langsung disimpan pada variabel nullable tanpa konversi. Perbedaannya dengan operator bang terletak pada arah kepastiannya, karena ?. tidak pernah gagal sedangkan operator bang menuntut nilai yang pasti ada.

Penjaga ?. melindungi tepat satu akses, yaitu akses yang ditandainya, dan bukan seluruh rantai sejak awal. Pada ekspresi mesin.log?.length dengan mesin bertipe nullable, akses log di depan tanda ?. tetap ditolak analyzer karena tidak ada penjaga yang melindunginya. Bentuk yang benar menempatkan ?. pada akses pertama yang berpotensi null, sehingga penjaga itu beserta seluruh akses di belakangnya berhenti bersama-sama ketika nilai awalnya null.

Nilai bawaan dengan operator ??#

Operator ?? mengembalikan operan kirinya ketika nilainya tidak null dan operan kanannya ketika nilai tersebut null. Bentuk ini menyediakan nilai bawaan (default value) yang berlaku khusus untuk keadaan null. Kombinasinya dengan ?. menghasilkan pola yang umum, yaitu mengakses secara aman terlebih dahulu, lalu menyediakan teks cadangan apabila hasilnya null.

Program berikut membaca konfigurasi pada peta yang hanya memuat kunci mode, sehingga pembacaan kunci tema selalu jatuh pada nilai cadangan. Variabel stok diletakkan di tingkat atas agar keadaannya tidak dipromosikan analisis alur, lalu variabel tersebut menunjukkan perilaku penugasan bersyarat dengan ??=.

dart nilai-bawaan.dart
String? bacaKonfigurasi(String kunci) {
  final nilai = {"mode": "teks"};
  return nilai[kunci];
}

int? stok;

void main() {
  final mode = bacaKonfigurasi("mode") ?? "default";
  final tema = bacaKonfigurasi("tema") ?? "gelap";
  print("mode: $mode");
  print("tema: $tema");
  print(bacaKonfigurasi("tema")?.toUpperCase() ?? "GELAP");

  stok ??= 24;
  print("stok: $stok");
  stok ??= 100;
  print("stok: $stok");
}
bash
# menjalankan program dari folder topik
dart run nilai-bawaan.dart
text Keluaran
mode: teks
tema: gelap
GELAP
stok: 24
stok: 24

Kunci tema tidak ada pada peta, sehingga ekspresi bacaKonfigurasi("tema") ?? "gelap" menghasilkan gelap. Rantai yang lebih panjang tetap terbaca, sebagaimana bacaKonfigurasi("tema")?.toUpperCase() ?? "GELAP" yang memeriksa null dua kali secara berurutan. Pada penugasan, operator ??= mengisi variabel hanya ketika nilainya masih null, sehingga baris stok ??= 100 tidak mengganti nilai 24 yang sudah ada. Bentuk ini lebih ringkas daripada pernyataan if yang memeriksa null secara manual.

Operator ?? juga dapat dirangkai lebih dari dua operan, misalnya a ?? b ?? c yang mengembalikan operan pertama yang tidak null. Pemeriksaan berjalan dari kiri ke kanan, sehingga operan di sebelah kanan tidak dievaluasi apabila operan di kirinya sudah bernilai. Sifat ini menjaga panggilan yang mahal tetap tidak dijalankan ketika nilainya tidak dibutuhkan.

Cascade null-aware ?..#

Cascade .. menjalankan beberapa panggilan pada satu objek yang sama tanpa mengulang nama variabelnya. Versi null-aware ?.. menambahkan satu jaminan pada rangkaian tersebut, yaitu seluruh panggilan dilewati ketika objek targetnya null. Nilai kembalian cascade adalah objek targetnya, sehingga beberapa panggilan dapat disusun berurutan pada satu ekspresi.

Kelas Papan pada contoh berikut menyimpan catatan piket beserta statusnya. Variabel papan diletakkan di tingkat atas dengan tipe Papan?, mula-mula bernilai null, dan baru diisi objek Papan() setelah percobaan pertama. Penempatan di tingkat atas membuat keadaannya tidak dipromosikan analisis alur, sehingga kedua keadaan itu benar-benar terjadi saat program berjalan. Jalankan programnya untuk melihat kedua keadaan itu.

dart papan-catatan.dart
class Papan {
  final List<String> catatan = [];
  String status = "kosong";

  void tulis(String teks) {
    catatan.add(teks);
    status = "ada catatan";
  }

  void bersihkan() {
    catatan.clear();
    status = "kosong";
  }
}

Papan? papan;

void main() {
  papan?..bersihkan()..tulis("ada pemanggilan pada null");
  print(papan == null);

  papan = Papan();
  papan?..tulis("Ali piket hari Senin")..tulis("Budi piket hari Rabu");
  print(papan?.catatan);
  print(papan?.status);
}
bash
# menjalankan program dari folder topik
dart run papan-catatan.dart
text Keluaran
true
[Ali piket hari Senin, Budi piket hari Rabu]
ada catatan

Rangkaian pertama tidak mengeksekusi bersihkan() maupun tulis() karena papan masih null, sehingga pengecekan papan == null tetap bernilai true. Rangkaian kedua berjalan sepenuhnya setelah papan diberi objek Papan(), dan kedua catatan beserta statusnya muncul pada keluaran. Pembacaan papan?.catatan dan papan?.status tetap dijaga ?. karena variabel tingkat atas tidak dipromosikan analisis alur, sehingga setiap pembacaan diperlakukan sebagai berpotensi null meskipun objeknya baru saja diisi. Tanpa ?.., keadaan pertama harus ditangani dengan pernyataan if eksplisit sebelum rangkaian cascade.

Penanda ?.. cukup ditulis pada panggilan pertama, sedangkan panggilan berikutnya tetap menggunakan .. karena seluruh rangkaian tunduk pada pemeriksaan yang sama. Pola ini cocok untuk sumber daya yang mungkin belum dibuat, misalnya memanggil bersihkan() pada papan yang belum dialokasikan atau menutup sesi yang mungkin belum dimulai. Apabila setiap panggilan perlu dijaga secara terpisah dari yang lain, penulisan beberapa akses ?. lebih tepat daripada satu rangkaian cascade.

Nilai dari ekspresi cascade null-aware juga mengikuti keadaan targetnya. Ketika target tidak null, ekspresi menghasilkan objek target itu sendiri, dan ketika target null, seluruh ekspresi bernilai null. Karena itu, hasil papan?..tulis("catatan") bertipe Papan? dan tetap dapat digabungkan dengan operator ?? apabila nilai cadangan diperlukan. Perilaku ini menegaskan bahwa keputusan menjalankan atau melewati seluruh rangkaian sepenuhnya ditentukan oleh keadaan targetnya.

Ringkasan#

  • Operator ?. mengubah akses pada nilai null menjadi null, bukan error saat berjalan.
  • Operator ?? menyediakan nilai bawaan, sedangkan ??= mengisi variabel hanya ketika nilainya masih null.
  • Cascade ?.. melewati seluruh rangkaian panggilan ketika objek targetnya null.
  • Ketiga operator dapat dirangkai, misalnya nilai?.panjang ?? 0 yang menghasilkan 0 ketika nilai null.

Referensi#