dartnull-safetyoperatorMahir3 mnt baca

Operator ??= Mendalam

Isi-jika-kosong: lazy init, default config, dan pola cache.

Operator kecil dengan kegunaan besar

??= terlihat sepele: "isi jika masih null". Tapi ia adalah fondasi tiga pola penting: inisialisasi malas, default konfigurasi, dan cache. Memahaminya dalam mengubah kode verbose menjadi elegan.

Analogi: ??= itu seperti pramutama hotel yang hanya mengantar handuk jika kamar belum ada handuknya. Tidak mengecek dua kali, tidak mengantar dobel.

Bentuk dasar dan evaluasi malas

dart
void main() {
  String? nama;

  nama ??= 'Tamu';
  print(nama); // Tamu

  nama ??= 'Bos';
  print(nama); // tetap Tamu, tidak ditimpa
}

Poin krusial: sisi kanan hanya dievaluasi jika kiri null (lazy). Ini bukan sekadar gula sintaks untuk if.

dart
String hitungDefault() {
  print('Menghitung default yang mahal...');
  return 'default';
}

void main() {
  String? a = 'sudah ada';
  a ??= hitungDefault(); // TIDAK mencetak apa-apa!
  print(a); // sudah ada

  String? b;
  b ??= hitungDefault(); // mencetak 'Menghitung...'
  print(b); // default
}

Bandingkan dengan a = a ?? hitungDefault() yang ekuivalen. Keduanya malas, tapi ??= lebih ringkas.

Pola 1: default konfigurasi

dart
class Config {
  String? host;
  int? port;
  bool? debug;

  void terapkanDefault() {
    host ??= 'localhost';
    port ??= 8080;
    debug ??= false;
  }

  @override
  String toString() => '$host:$port (debug=$debug)';
}

void main() {
  var c1 = Config();
  c1.terapkanDefault();
  print(c1); // localhost:8080 (debug=false)

  var c2 = Config()
    ..host = 'api.contoh.com'
    ..port = 443;
  c2.terapkanDefault();
  print(c2); // api.contoh.com:443 (debug=false)
}

Pengguna mengisi yang ia mau, sisanya default. Tanpa ??=, tiap field butuh if 3 baris.

Pola 2: cache / memoization

dart
class DataService {
  Map<String, String>? _cache;

  String ambil(String kunci) {
    // Buat map hanya saat pertama dibutuhkan
    _cache ??= {};
    // Simulasi fetch mahal
    return _cache!.putIfAbsent(kunci, () => 'data-untuk-$kunci');
  }
}

void main() {
  var s = DataService();
  print(s.ambil('user1')); // data-untuk-user1 (dihitung)
  print(s.ambil('user1')); // data-untuk-user1 (dari cache)
}

_cache ??= {} membuat objek berat hanya sekali, saat pertama dibutuhkan. Ini lazy initialization manual.

Pola 3: akumulasi kondisional

dart
void main() {
  Map<String, List<String>> grup = {};
  var data = [('a', 'apel'), ('b', 'pisang'), ('a', 'alpukat')];

  for (var (kunci, nilai) in data) {
    // Pastikan list ada, lalu tambah
    (grup[kunci] ??= []).add(nilai);
  }
  print(grup); // {a: [apel, alpukat], b: [pisang]}
}

(grup[kunci] ??= []) mengembalikan list (yang baru dibuat atau yang sudah ada), lalu .add langsung. Elegan untuk grouping.

Kesalahan umum

1. ??= pada non-nullable

dart
void main() {
  String nama = 'Budi';
  // nama ??= 'Ani'; // WARNING: tidak ada efek, nama tak mungkin null
}

??= hanya bermakna untuk nullable. Pada non-nullable ia mati suri.

2. Mengira sisi kanan selalu dievaluasi

dart
void main() {
  String? s = 'ada';
  s ??= (() {
    print('efek samping!');
    return 'x';
  })();
  // 'efek samping!' TIDAK tercetak
}

Jika fungsi kanan punya efek samping yang kamu harapkan terjadi, jangan taruh di ??=.

3. ??= dengan properti yang punya setter kompleks

Jika setter melakukan lebih dari menyimpan (validasi, notifikasi), ??= tetap memanggil setter saat mengisi. Pahami itu, bukan bug, tapi perlu disadari.

Kesimpulan

??= = isi-jika-null dengan evaluasi malas. Tiga pola: default config, lazy cache, akumulasi kondisional. Operator kecil, dampak besar pada kerapian kode null-safe.

Catatan teknis: a ??= b dievaluasi sebagai: baca a sekali, jika null tulis b. Untuk properti dengan getter/setter, getter dipanggil sekali dan setter maksimal sekali. Ini penting untuk properti dengan efek samping.

Tantangan

Config dengan default

Buat class Pengaturan dengan field nullable tema, bahasa, notif. Method isiDefault() memakai ??= untuk 'terang', 'id', true. Uji: objek kosong → semua default; objek dengan tema='gelap' → tema tetap gelap.

text
class Pengaturan {
  String? tema;
  String? bahasa;
  bool? notif;

  void isiDefault() {
    // TODO: ??=
  }
}

void main() {
  var p1 = Pengaturan()..isiDefault();
  var p2 = Pengaturan()
    ..tema = 'gelap'
    ..isiDefault();
  // TODO: cetak keduanya
}