Null safety mewajibkan setiap variabel non-nullable memiliki nilai yang nyata, tetapi tidak semua nilai tersedia sejak baris pertama. Kata kunci late mengizinkan deklarasi yang penugasannya atau inisialisasinya dilakukan kemudian, dengan konsekuensi yang dinyatakan secara tegas oleh sistem tipe. Topik ini membahas deklarasi tanpa nilai awal, error ketika variabel dibaca terlalu dini, inisialisasi tertunda pada anggota kelas, serta kontrak satu penugasan pada late final.
Deklarasi tanpa nilai awal#
Deklarasi variabel non-nullable tanpa nilai awal ditolak analyzer pada semua tingkatan. Variabel tingkat atas dan anggota kelas wajib diinisialisasi segera, sedangkan variabel lokal wajib ditugaskan sebelum dibaca. Berkas berikut memuat kedua bentuk pelanggaran tersebut, dan pemeriksaan dart analyze menampilkan pesan yang menyertainya.
# membuat berkas dengan deklarasi non-nullable tanpa nilai awal
mkdir -p /tmp/uji/late-dan-inisialisasi
cat > /tmp/uji/late-dan-inisialisasi/tanpa-late.dart <<'EOF'
String atas;
void main() {
String lokal;
print(lokal);
}
EOF
# memeriksa berkas dengan analyzer
dart analyze /tmp/uji/late-dan-inisialisasi/tanpa-late.dartAnalyzing tanpa-late.dart...
error - tanpa-late.dart:1:8 - The non-nullable variable 'atas' must be initialized. Try adding an initializer expression. - not_initialized_non_nullable_variable
error - tanpa-late.dart:5:9 - The non-nullable local variable 'lokal' must be assigned before it can be used. Try giving it an initializer expression, or ensure that it's assigned on every execution path. - not_assigned_potentially_non_nullable_local_variable
2 issues found.Kedua pesan menyarankan pengisian nilai awal, dan keduanya tidak menyebut late sebagai pilihan. Kata kunci ini menawarkan jalan keluar yang lain, karena penugasan yang ditunda secara sadar membuat deklarasi tanpa nilai awal menjadi sah. Program berikut menggunakan variabel nama yang dinyatakan late, ditugaskan di dalam main, lalu dibaca setelahnya.
late String nama;
void main() {
print("mulai");
nama = "Ali";
print(nama);
}# menjalankan program dari folder topik
dart run penugasan-tunda.dartmulai
AliVariabel nama bertipe String non-nullable, tetapi deklarasinya sah karena penandanya late. Penugasan dilakukan sebelum pembacaan pertama, sehingga program berjalan tanpa penolakan maupun error. Pola ini cocok untuk nilai yang hanya diketahui setelah program mulai berjalan, misalnya nama berkas yang diterima dari argumen baris perintah.
Penugasan tertunda tidak wajib terjadi di awal main. Variabel late dapat diisi oleh fungsi mana pun selama penugasan itu terjadi sebelum pembacaan pertama, termasuk oleh fungsi inisialisasi yang dipanggil dari tempat lain. Karena variabel late biasa boleh ditugaskan ulang, nilai yang digunakan selalu merupakan penugasan terakhir.
LateInitializationError ketika dibaca terlalu dini#
Kebebasan menunda penugasan membawa konsekuensi yang jelas. Variabel late yang dibaca sebelum ditugaskan berada pada keadaan tanpa nilai, dan Dart meresponsnya dengan error saat berjalan. Error ini bernama LateInitializationError dan menghentikan eksekusi program pada baris pembacaan tersebut.
Berkas berikut membaca nama sebelum penugasannya pernah dilakukan. Perintahnya menulis berkas, menjalankannya, lalu menampilkan keluaran yang sebenarnya tercetak.
# membuat berkas yang membaca variabel late sebelum ditugaskan
mkdir -p /tmp/uji/late-dan-inisialisasi
cat > /tmp/uji/late-dan-inisialisasi/belum.dart <<'EOF'
late String nama;
void main() {
print("mulai");
print(nama);
}
EOF
# menjalankan berkas untuk melihat error saat berjalan
dart run /tmp/uji/late-dan-inisialisasi/belum.dartmulai
Unhandled exception:
LateInitializationError: Field 'nama' has not been initialized.
#0 nama (file:///tmp/uji/late-dan-inisialisasi/belum.dart)
#1 main (file:///tmp/uji/late-dan-inisialisasi/belum.dart:5:9)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19)
#3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)Pesan LateInitializationError: Field 'nama' has not been initialized. menyebut kata Field karena variabel nama berada di tingkat atas. Eksekusi berhenti sebelum nilai tercetak, dan hanya baris mulai yang sempat dijalankan. Perbandingan dengan program penugasan-tunda.dart menunjukkan bahwa penugasan nama = "Ali" sebelum pembacaan pertama adalah pembeda antara program yang berjalan dan program yang berhenti.
Kesalahan semacam itu dapat dihilangkan dengan memberi inisialisator pada deklarasinya. Bentuk late String nama = "Ali" tetap ditandai late, tetapi penugasan inisialisatornya berjalan otomatis pada pembacaan pertama, sehingga tidak ada lagi keadaan tanpa nilai yang dapat terbaca. Bentuk ini dipilih ketika nilai dapat dihitung pada saat pembacaan pertama terjadi, sedangkan deklarasi tanpa inisialisasi dipertahankan apabila penugasannya membutuhkan data yang baru tersedia kemudian, misalnya argumen baris perintah.
Inisialisasi tertunda pada anggota kelas#
Anggota kelas mendapat manfaat late yang paling terasa. Inisialisator anggota non-late dievaluasi pada saat objek dibuat, sedangkan inisialisator anggota late dievaluasi pada saat anggota itu pertama kali dibaca. Perbedaan waktu ini berguna ketika inisialisasi mahal, misalnya membaca perangkat atau berkas, tetapi hasilnya belum tentu digunakan selama hidup objek.
Program berikut membandingkan dua kelas sensor yang isinya sama, satu dengan anggota biasa dan satu dengan anggota late. Jalankan perintahnya dan perhatikan urutan baris keluaran yang menandakan waktu pembacaan sensor.
class SuhuBiasa {
final String pembacaan = bacaSensor();
}
class SuhuTunda {
late final String pembacaan = bacaSensor();
}
String bacaSensor() {
print("sensor dibaca");
return "27.5";
}
void main() {
SuhuBiasa();
print("objek biasa selesai dibuat");
final tunda = SuhuTunda();
print("objek tunda selesai dibuat");
print("pembacaan: ${tunda.pembacaan}");
print("pembacaan: ${tunda.pembacaan}");
}# menjalankan program dari folder topik
dart run sensor.dartsensor dibaca
objek biasa selesai dibuat
objek tunda selesai dibuat
sensor dibaca
pembacaan: 27.5
pembacaan: 27.5Kelas SuhuBiasa memicu sensor dibaca sebelum baris objek biasa selesai dibuat tercetak, karena inisialisatornya berjalan pada saat objek dibuat. Kelas SuhuTunda baru memicu pembacaan sensor ketika tunda.pembacaan diakses pertama kali. Nilai yang sudah dihitung disimpan pada anggota tersebut, sehingga pembacaan kedua tidak menjalankan inisialisator lagi.
Inisialisasi tertunda berlaku per objek, sehingga setiap instance memiliki keadaan pembacaan pertamanya sendiri. Selain itu, inisialisator anggota late boleh merujuk anggota lain dari objek yang sama, sesuatu yang tidak diizinkan pada inisialisator anggota biasa karena memicu error implicit_this_reference_in_initializer. Contohnya, bidang late final String pembacaan = "sensor-$id" dinyatakan sah meskipun id baru diisi melalui konstruktor, karena nilai pembacaan baru dihitung ketika bidang itu dibaca dan konstruktor sudah selesai dijalankan pada waktu tersebut.
late final dan penugasan tunggal#
Gabungan late final membentuk kontrak penugasan tunggal, yaitu variabel boleh ditunda tetapi hanya boleh diisi satu kali. Variabel semacam ini cocok untuk nilai yang ditentukan sekali di awal siklus hidup program, misalnya konfigurasi yang dibaca saat program mulai berjalan. Pelanggaran kontraknya terdeteksi pada waktu yang berbeda menurut jenis variabelnya.
Pada anggota kelas, penugasan kedua ditolak saat program berjalan. Contoh berikut menugaskan judul dua kali dan menangkap error yang muncul agar pesannya terlihat.
class Laporan {
late final String judul;
}
void main() {
final laporan = Laporan();
laporan.judul = "Laporan harian";
print(laporan.judul);
try {
laporan.judul = "Laporan lain";
} catch (e) {
print("tertangkap: $e");
print("tipe: ${e.runtimeType}");
}
print(laporan.judul);
}# menjalankan program dari folder topik
dart run satu-kali.dartLaporan harian
tertangkap: LateInitializationError: Field 'judul' has already been initialized.
tipe: LateError
Laporan harianPesan LateInitializationError: Field 'judul' has already been initialized. menyatakan bahwa penugasan kedua melanggar kontrak. Objek errornya bertipe LateError, yaitu kelas yang mewujudkan LateInitializationError pada saat berjalan. Nilai judul tetap berisi teks pertama, sehingga keadaan program tidak berubah meskipun penugasannya ditolak. Kontrak satu penugasan inilah yang membedakan late final dari late biasa. Nilai yang ditentukan sekali oleh keadaan yang baru tersedia setelah objek dibuat, misalnya hasil pembacaan konfigurasi, cocok ditampung pada anggota late final.
Pada variabel lokal, analyzer menolak penugasan kedua sebelum program sempat dijalankan. Perbedaan ini muncul karena analisis alur dapat melacak seluruh keadaan variabel lokal, sedangkan keadaan anggota kelas dapat berubah dari tempat lain.
# membuat berkas dengan penugasan ganda pada late final lokal
mkdir -p /tmp/uji/late-dan-inisialisasi
cat > /tmp/uji/late-dan-inisialisasi/latefinal-lokal.dart <<'EOF'
void main() {
late final String judul;
judul = "Laporan harian";
judul = "Laporan lain";
print(judul);
}
EOF
# memeriksa berkas dengan analyzer
dart analyze /tmp/uji/late-dan-inisialisasi/latefinal-lokal.dartAnalyzing latefinal-lokal.dart...
error - latefinal-lokal.dart:4:3 - The late final local variable is already assigned. Try removing the 'final' modifier, or don't reassign the value. - late_final_local_already_assigned
1 issue found.Ringkasan#
- Kata kunci late mengizinkan deklarasi tanpa nilai awal, dengan penugasan atau inisialisasi yang dilakukan kemudian.
- Pembacaan sebelum penugasan memunculkan
LateInitializationErrorsaat program berjalan. - Inisialisator anggota late dievaluasi pada pembacaan pertama dan hasilnya disimpan untuk pembacaan berikutnya.
late finalmenjamin penugasan tunggal, ditolak saat pemeriksaan untuk variabel lokal dan saat berjalan untuk anggota kelas.
Referensi#
- Variables — dart.dev — Dokumentasi kata kunci late beserta aturan inisialisasi variabel Dart.
- Understanding null safety — dart.dev — Latar ditambahkannya late sebagai bagian dari null safety.
- Sound null safety — dart.dev — Ringkasan resmi fitur null safety Dart.