`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.
| Elan | Nə vaxt hesablanır | Təkrar mənimsətmə | Risk |
|---|---|---|---|
| `final String s = ...;` | Konstruksiyada | Yox | Yoxdur |
| `String? s;` | — | Bəli | Hə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) | Yox | Gö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ə
thishə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ıqthisvar
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,lateisə yoxluğu xətaya çevirir - Konstruktora parametr əlavə etməmək üçün
lateyazmaq — bu, sadəcə compile-time yoxlamasını runtime crash-ə köçürür - Bir sinif bir neçə vəziyyəti təsvir edirsə — burada
sealediyerarxiya 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
- Dəyişənlər: laterəsmidart.dev