Sparround

`late` və initialization

late compiler-ə deyir: "bu non-nullable dəyişən elan anında hazır deyil, amma ilk oxunuşdan əvvəl mütləq mənimsədiləcək — yoxlamanı compile-time-dan runtime-a köçür."

Texniki olaraq late sahə bir gizli bayraq əlavə edir: hər oxunuşda "initialise olunub?" yoxlanılır. Olunmayıbsa LateInitializationError atılır. Yəni late null safety-ni söndürmür, sadəcə yoxlamanın vaxtını dəyişir.

İki fərqli istifadə forması var və onları qarışdırmamaq vacibdir:

  • İnitializer-siz `late`: late String token; — dəyər sonradan (məsələn, initState-də və ya bir setup metodunda) mənimsədilir.
  • İnitializer-li `late` (lazy): late final config = _loadConfig(); — ifadə yalnız ilk oxunuşda hesablanır və nəticə saxlanılır. Heç vaxt oxunmasa, ümumiyyətlə hesablanmır.

İkinci forma bahalı obyektlər üçün əla alətdir: ağır parse, böyük RegExp, şifrələmə açarı və ya yalnız bəzi kod yollarında lazım olan servis.

ElanNə vaxt hesablanırTəkrar mənimsətməRisk
`final String s = ...;`KonstruksiyadaYoxYoxdur
`String? s;`BəliHər istifadədə yoxlama və ya `!`
`late String s;`Manual mənimsətmədəBəli`LateInitializationError`
`late final String s;`Manual mənimsətmədəYox — ikinci dəfə runtime xətası`LateInitializationError`
`late final s = compute();`İlk oxunuşda (lazy)YoxGözlənilməz vaxtda yan effekt

Sinifdə initialization sırası late-i doğru istifadə etmək üçün bilinməlidir. Dart konstruktoru bu ardıcıllıqla işləyir:

  • Sahələrin initializer-ləri (elan yanında yazılanlar) — bu mərhələdə this hələ mövcud deyil, ona görə başqa sahəyə və ya metoda müraciət etmək olmaz
  • Konstruktorun initializer siyahısı (: x = 1, y = 2) və super çağırışı
  • Konstruktorun gövdəsi ({ ... }) — burada artıq this var

Buradan late-in ən tipik doğru istifadəsi çıxır: sahə başqa sahədən asılıdırsa, adi initializer işləmir, çünki this hazır deyil. late final parser = Parser(config); isə işləyir, çünki ifadə yalnız ilk oxunuşda — obyekt tam qurulduqdan sonra — hesablanır.

Digər tez-tez rast gəlinən istifadə: late final sahə konstruktorun gövdəsində mənimsədilir (məsələn, late final controller; ... controller = Controller(this);), çünki this-ə referens lazımdır.

`late` nə vaxt düzgün alətdir, nə vaxt problemi gizlədir?

Düzgün istifadə:

  • Bahalı dəyərin lazy hesablanması (late final regex = RegExp(...))
  • Başqa sahədən və ya this-dən asılı sahə
  • Framework lifecycle: obyekt konstruktorda deyil, sonrakı setup mərhələsində hazır olur (Flutter-da initState, testlərdə setUp)
  • Dependency injection: sahə yaradılışdan sonra bir dəfə "wire" olunur

Problemi gizlədən istifadə:

  • Sahə həqiqətən opsionaldırsa — T? düzgün ifadədir, late isə yoxluğu xətaya çevirir
  • Konstruktora parametr əlavə etməmək üçün late yazmaq — bu, sadəcə compile-time yoxlamasını runtime crash-ə köçürür
  • Bir sinif bir neçə vəziyyəti təsvir edirsə — burada sealed iyerarxiya lazımdır

Bir cümləlik meyar: `late` "gec, amma mütləq olacaq" deməkdir; "bəlkə heç olmayacaq" halı üçün `T?` işlədilməlidir.

Interview ipucu. "late nə edir?" sualına "non-nullable dəyişəni sonradan mənimsətməyə imkan verir" cavabı yarımçıqdır. Güclü cavab mexanizmi göstərir: "`late` compile-time yoxlamasını runtime-a köçürür — gizli bayraq əlavə olunur, hər oxunuşda initialise olunub-olunmadığı yoxlanılır, olunmayıbsa `LateInitializationError` atılır. İnitializer ilə yazılsa, ifadə lazy olur və yalnız ilk oxunuşda hesablanır."

Davamı adətən belə olur: "`late` yoxsa `T?` — necə seçirsən?" Hazır meyarla cavab ver: "`late` — dəyər mütləq olacaq, amma gec; `T?` — dəyər ola da bilər, olmaya da. `late`-i sadəcə `!` yazmamaq üçün işlətmək səhvdir, çünki nəticə eyni crash-dir, sadəcə mesajı fərqlidir."

📚 Mənbələr və sənədlər