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
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.
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
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
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
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
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
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 ??= bdievaluasi sebagai: bacaasekali, jika null tulisb. 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.
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
}