lateinit / lazy: gecikmiş initallaşdirmənin mahiyyəti və mexanizmləri Kotlin-də

Müəllif: IT Sectr Dərc olunub: 2026-06-23 Oxuma vaxtı: 8 dəq

Gecikmiş initallaşdırma (lazy initialization) — Kotlin-də obyektin xassəsinin yaradılma anında deyil, ona ilk müraciət edildikdə initallaşdırıldığı mexanizmdir. JetBrains, 2024 məlumatlarına görə, lateinit və lazy bu strategiyanı həyata keçirmək üçün iki daxili alətdir. Hər ikisi initallaşdırmanın təxirə salınması problemini həll edir, lakin iş mexanizminə və tətbiq sahəsinə görə prinsipial olaraq fərqlənir.

Əsas məqamlar

  • lateinit — obyekt yaradıldıqdan sonra initallaşdırmaya icazə verən var-xassələr üçün modifikator
  • lazy — ilk müraciətdə dəyəri initallaşdıran val-xassələr üçün deleqat
  • lateinit var tələb edir və JVM primitiv tiplərini dəstəkləmir
  • lazy defolt olaraq thread-safe-dir və hesablanmış nəticəni keşləyir
  • lateinit vaxtından əvvəl girişdə UninitializedPropertyAccessException atır

Kotlin-də gecikmiş initallaşdırma nədir?

Gecikmiş initallaşdırma — sinif xassəsinin dəyəri obyektin qurulması anında deyil, daha sonra, tələb əsasında aldığı nümunədir. Kotlin-də bu nümunə iki prinsipial fərqli şəkildə həyata keçirilir: lateinit modifikatoru və lazy deleqatı.

Hər iki mexanizm ümumı problemi həll edir — xassə sinifdə mövcud olmalıdır, lakin onun dəyəri ya obyektin yaradılması anında hələ məlum deyil, ya da onun hesablanması ehtiyacsız yerinə yetirmək üçün çox resurs tutur. Google I/O 2023 məlumatlarına görə, tipik Android tətbiqində xassələrin 40%-ə qədəri gecikmiş initallaşdırma vasitəsilə optimallaşdırıla bilər ki, bu da başlanma vaxtını 15–25% azaldır.

lateinit və lazy arasında seçim üç amillə müəyyən edilir: xassənin dəyişkənliyi (var və ya val), onun ömür müddəti (birdəfəlik və ya çoxdəfəlik təyinat) və thread-safe tələbləri (təkthreadli və ya çoxthreadli giriş).

Gecikmiş initallaşdırma nə vaxt tətbiq edilir

Birinci və ən çox yayılmış ssenari — Dependency Injection. Freymvork (Dagger, Hilt, Koin) obyekt yaradıldıqdan sonra asılılıqları inject edir, buna görə də xassə konstruktorda initallaşdırıla bilməz. lateinit olmadan bütün asılılıqları nullable elan etməli və hər istifadədə onları yoxlamalı olardıq.

İkinci ssenari — ağır resurslar: verilənlər bazası, şəbəkə klienti, fayl meneceri. Onların yaradılması vaxt və yaddaş tələb edir, buna görə də yalnız faktiki istifadə zamanı initallaşdırılmalıdırlar. lazy bu cür hallar üçün idealdır, birdəfəlik yaradılmanı təmin edir.

Üçüncü vəziyyət — Android komponentləri (Activity, Fragment, ViewModel), həyat dövrü əməliyyat sistemi tərəfindən idarə olunur. onCreate, onViewCreated və ya ViewModel init blokundan asılı olan xassələr konstruktorda initallaşdırıla bilməz.

lateinit: mexanizm və məhdudiyyətlər

lateinit — Kotlin kompilyatoruna initallaşdırmanı təxirə salmağa icazə verən var-xassələr üçün modifikatordur. Kompilyator konstruktorda dəyər təyin etməyi tələb etmir, lakin hər girişdə runtime yoxlaması yaradır: əgər xassə initallaşdırılmayıbsa, UninitializedPropertyAccessException atılır.

kotlin
class MainActivity {
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

lateinit məhdudiyyətləri: xassə var (val yox), nullable olmayan, primitiv tip olmayan (Int, Double, Boolean və s.) elan edilməlidir. Səbəb — primitiv tiplər JVM primitivlərinə kompilyasiya olunur ki, onların «initallaşdırılmayıb» vəziyyəti yoxdur. Nullable xassələr üçün gecikmiş initallaşdırma lazım deyil: null artıq dəyərin olmaması deməkdir.

lateinit xassəsinin vəziyyətini yoxlamaq üçün :: operatoru vasitəsilə daxili keçid istifadə olunur: ::propertyName.isInitialized. Bu, istisna alma riski olmadan xassənin initallaşdırılıb-initiallaşdırılmadığını yoxlamağın yeganə təhlükəsiz yoludur. Yoxlama yalnız eyni sinifdən və ya daxili sinifdən mövcuddur, xarici koddan deyil.

kotlin
class LoginFragment {
    lateinit var binding: FragmentLoginBinding

    fun isReady(): Boolean {
        return ::binding.isInitialized
    }
}

lateinit performansı

lateinit initallaşdırmadan sonra əlavə yük əlavə etmir: dəyər təyin edildikdən sonra xassəyə giriş birbaşa sahəyə müraciətlə eynidir. Yeganə xərc — təyinatdan əvvəl hər oxumada initallaşdırma yoxlamasıdır. Initallaşdırmadan sonra JIT kompilyatoru yoxlamanı optimallaşdırır.

Vacib xüsusiyyət: lateinit xassələri inline siniflərdə istifadə edilə bilməz və xüsusi getter/setter olan xassələr üçün dəstəklənmir. Əgər xassə hesablama girişi tələb edirsə — lateinit əvəzinə lazy istifadə edin.

lazy: mexanizm və üstünlüklər

lazy — Kotlin standart kitabxanasına daxil edilmiş xassə deleqatıdır. O, xassəyə ilk müraciətdə dəyəri hesablayır və bütün sonrakı çağırışlar üçün nəticəni keşləyir. lateinit-dən fərqli olaraq, lazy yalnız val ilə işləyir, xassəni initallaşdırmadan sonra dəyişməz edir.

kotlin
class UserRepository {
    private val database: Database by lazy {
        Database.create("users.db")
    }

    fun getUser(id: String): User {
        return database.query("SELECT * FROM users WHERE id = ?", id)
    }
}

lazy thread-safe mexanizmini idarə edən isteğe bağlı LazyThreadSafetyMode parametrini qəbul edir. Defolt olaraq SYNCHRONIZED istifadə olunur — ikiqat yoxlama bloklaması (Double-Checked Locking), bir neçə thread-dən eyni vaxtda girişdə belə birdəfəlik initallaşdırmanı təmin edir.

lazy thread-safe rejimləri

PUBLICATION rejimi paralel initallaşdırmaya icazə verir: bir neçə thread eyni vaxtda initallaşdırma blokunu yerinə yetirə bilər, lakin nəticə yalnız birinci bitirəndən qəbul edilir. Bu, yüksək rəqabətdə SYNCHRONIZED-dən daha sürətlidir, lakin resurs sərfini artırır.

NONE rejimi sinxronizasiyanı tamamilə söndürür. Yalnız girişi təmin edilmiş şəkildə bir thread-dən baş verən xassələr üçün istifadə edin. Bu rejimdə lazy minimal əlavə yüklə işləyir — praktiki olaraq birbaşa təyinat kimi.

kotlin
val heavyConfig: Config by lazy(LazyThreadSafetyMode.NONE) {
    Config.loadFromFile("config.json")
}

Lazy nə vaxt üstünlük təşkil edir

lazy birdəfəlik initallaşdırılan asılılıqlar üçün düzgün seçimdir: repozitoriyalar, şəbəkə müştəriləri, keşlər, verilənlər bazaları. Val semantikası təsadüfi yenidən yazmadan qoruyur, defolt thread-safe isə kodu çoxthreadli mühitdə təhlükəsiz edir. lazy həmçinin lateinit ilə mümkün olmayan primitiv tiplərlə düzgün işləyir.

Android-də lazy tez-tez by viewModels() vasitəsilə ViewModel asılılıqlarının initallaşdırılması və ya Retrofit müştərilərinin yaradılması üçün istifadə olunur. Bununla belə, ehtiyatlı olun: əgər lazy bloku Activity və ya Fragment istinadını tutursa, bu, yaddaş sızmasına səbəb ola bilər, çünki deleqat xassənin ömrünün sonuna qədər closure saxlayır.

lateinit vs lazy: yanaşmaların müqayisəsi

lateinitlazy arasında seçim — bu, üstünlük məsələsi deyil, xassənin xarakteri ilə müəyyən edilən memarlıq qərarıdır. Hər bir mexanizm öz vəzifəsini həll edir və onların tətbiq sahələri yalnız qismən kəsişir.

Kriteriyalateinitlazy
Xassə tipiyalnız varyalnız val
Nullableqadağandıricazə verilir
Primitiv tiplərqadağandıricazə verilir
Thread-safetəmin edilmirSYNCHRONIZED defolt
Vəziyyət yoxlaması::x.isInitializedtələb olunmur
Səhv zamanı istisnaUninitializedPropertyAccessExceptioninitallaşdırma blokunda səhv
Keşləmətətbiq edilmirbirdəfəlik hesablama
Android BindingView Binding, Data Bindingistifadə edilmir
DI freymvorklarıDagger, Hilt, Koinəl ilə inject etmə

lateinit istifadə edin, xassə initallaşdırmadan sonra dəyişməli olduqda və ya onun yaradılması xarici kod tərəfindən idarə edildikdə. Tipik nümunə — Android Activity-də View Binding: binding onCreate-də yaradılır, lakin var olaraq qalır, çünki freymvork bu ssenari üçün val-ı dəstəkləmir.

lazy istifadə edin, xassə birdəfəlik initallaşdırıldıqda, onun hesablanması bahalı olduqda və dəyər obyektin ömrü boyu dəyişmədikdə. Klassık nümunə — repozitoriyaya ilk müraciətdə Retrofit müştərisinin və ya Room verilənlər bazasının tənbəl yaradılması.

lateinit və lazy-nin birləşdirilməsi

Bir sinifdə hər iki mexanizm eyni vaxtda istifadə edilə bilər. Məsələn, View Binding üçün lateinit və repozitoriya üçün lazy. Bu, müxtəlif xassələrə müxtəlif tələbləri əks etdirən normal təcrübədir. Əsas odur ki, semantikanı qarışdırmayın: val lazım olan yerdə lateinit istifadə etməyin və yenidən yazılmalı olan xassələr üçün lazy istifadə etməyin.

lateinit və lazy istifadəsində tipik səhvlər

lateinit ilə ən çox yayılmış səhv — xassə initallaşdırılmamışdan əvvəl ona müraciət etməkdir. Bu, kompilyasiya mərhələsində tutulmayan UninitializedPropertyAccessException-a gətirib çıxarır, çünki Kotlin düzgün initallaşdırma ardıcıllığına görə tərtibatçıya etibar edir. Həll — qeyri-müəyyən vəziyyətlərdə girişdən əvvəl həmişə ::property.isInitialized vasitəsilə vəziyyəti yoxlayın.

İkinci geniş yayılmış problem — semantik olaraq val olan xassələr üçün lateinit istifadə etməkdir. Əgər dəyər bir dəfə təyin olunur və daha dəyişmirsə, lazy daha düzgün seçimdir. O, xassəni dəyişməz edir, təsadüfi yenidən yazmanı istisna edir və pulsuz olaraq thread-safe əlavə edir.

Üçüncü səhv — yan təsirləri olan lazy. Lazy initallaşdırma bloku xarici vəziyyəti dəyişməməli və ya digər lazy xassələrinin initallaşdırma ardıcıllığına güvənməməlidir, çünki hesablamaların ardıcıllığı ilk müraciətdən asılıdır və göz qabağında olmaya bilər. Əgər lazy xassələri bir-birinə istinad edirsə, bu, tsiklik asılılığa və StackOverflowError-a gətirib çıxarır.

Dördüncü problem — Android-də lazy vasitəsilə yaddaş sızması. Əgər lazy bloku Activity və ya Fragment istinadını tutursa — deleqat closure saxlayır və garbage collector hətta komponent məhv edildikdən sonra da onu boşalda bilmir. Həll — lazy-ni yalnız qısa ömürlü obyektlərlə istifadə edin və ya Activity əvəzinə Application kontekstini ötürün.

Beşinci tipik səhv — primitiv tiplərə lateinit tətbiq etmək cəhdidir. Kotlin kompilyatoru bunu sintaksis səviyyəsində bloklayır, lakin tərtibatçılar nullable sarğıları vasitəsilə məhdudiyyəti keçməyə çalışırlar. Bu, lazımsız null yoxlamalarına gətirib çıxarır və gecikmiş initallaşdırmanın üstünlüklərini tamamilə itirir.

Tez-tez verilən suallar

Kotlin-də lateinit və lazy arasında fərq nədir?

lateinit — konstruktordan sonra initallaşdırmaya icazə verən var-xassələr üçün modifikator. lazy — ilk müraciətdə dəyəri hesablayan və onu keşləyən val-xassələr üçün deleqat. lateinit primitiv tipləri və nullable-ı dəstəkləmir, lazy isə defolt olaraq thread-safe-dir.

lateinit xassəsinin initallaşdırıldığını yoxlamaq olar?

Bəli, xassəyə daxili keçid vasitəsilə: ::propertyName.isInitialized. Metod xassə artıq initallaşdırılıbsa, true qaytarır. Bu, lateinit sahələri ilə işləyərkən UninitializedPropertyAccessException-dan qaçmağın yeganə təhlükəsiz yoludur.

Niyə lateinit primitiv tiplərlə istifadə edilə bilməz?

Primitiv tiplər — Int, Double, Boolean və digərləri — JVM primitivlərinə (int, double, boolean) kompilyasiya olunur ki, onların «initallaşdırılmayıb» vəziyyəti yoxdur. lateinit flag kimi null istifadə edir, primitivlər isə null ola bilməz, buna görə də mexanizm bu tiplər üçün fiziki olaraq həyata keçirilə bilməz.

Lazy üçün defolt thread-safe rejimi hansıdır?

Defolt olaraq LazyThreadSafetyMode.SYNCHRONIZED istifadə olunur — bir neçə thread-dən girişdə birdəfəlik initallaşdırmanı təmin edən ikiqat yoxlama bloklaması. Təkthreadli ssenarilər üçün NONE, yüksək rəqabət üçün PUBLICATION istifadə edin.

Android-də lateinit nə vaxt lazy əvəzinə istifadə edilməlidir?

Xassə initallaşdırmadan sonra dəyişməli olduqda və ya onun yaradılması freymvork tərəfindən idarə edildikdə. Tipik nümunə — Android Activity-də View Binding: binding onCreate-də yaradılır və var olmalıdır. Birdəfəlik initallaşdırılan val asılılıqları üçün lazy istifadə edin.

Xülasə

  • lateinit — nullable olmadan konstruktordan sonra initallaşdırmaya icazə verən Kotlin var-xassələri üçün modifikator
  • lazy — birdəfəlik hesablama və nəticənin avtomatik keşlənməsi ilə val üçün xassə deleqatı
  • lateinit initallaşdırmadan əvvəl girişdə UninitializedPropertyAccessException atır
  • lazy LazyThreadSafetyMode.SYNCHRONIZED vasitəsilə defolt olaraq thread-safe-dir
  • lateinit primitiv tiplər və nullable xassələrlə uyğun deyil
  • lazy Android-də closure-da konteksti tutarkən yaddaş sızmasına səbəb ola bilər
  • Dəyişən xassələr üçün lateinit, birdəfəlik initallaşdırılan val asılılıqları üçün lazy seçin

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun