lateinit / lazy: esența inițializării întârziate și mecanismele în Kotlin

Autor: IT Sectr Publicat: 2026-06-23 Timp de citire: 8 min

Inițializarea întârziată (lazy initialization) — un mecanism în Kotlin prin care proprietatea unui obiect este inițializată nu la momentul creării, ci la prima accesare. Conform datelor JetBrains, 2024, lateinit și lazy — două instrumente încorporate pentru implementarea acestei strategii. Ambele rezolvă problema amânării inițializării, dar diferă fundamental prin mecanismul de funcționare și domeniul de aplicare.

Principalele

  • lateinit — modificator pentru proprietăți var, care permite inițializarea după crearea obiectului
  • lazy — delegat pentru proprietăți val, care inițializează valoarea la prima accesare
  • lateinit necesită var și nu suportă tipuri primitive JVM
  • lazy este implicit thread-safe și cache-uiește rezultatul calculat
  • lateinit aruncă UninitializedPropertyAccessException la accesul prematur

Ce este inițializarea întârziată în Kotlin?

Inițializarea întârziată — este un model în care proprietatea unei clase primește valoarea nu la momentul construirii obiectului, ci mai târziu, la cerere. În Kotlin, acest model este implementat în două moduri fundamental diferite: modificatorul lateinit și delegatul lazy.

Ambele mecanisme rezolvă o problemă comună — proprietatea trebuie să existe în clasă, dar valoarea sa fie nu este încă cunoscută la momentul creării obiectului, fie calculul său este prea costisitor pentru a fi executat fără necesitate. Conform datelor Google I/O 2023, până la 40% din proprietățile dintr-o aplicație Android tipică pot fi optimizate prin inițializare întârziată, ceea ce reduce timpul de pornire cu 15–25%.

Alegerea între lateinit și lazy este determinată de trei factori: modificabilitatea proprietății (var sau val), durata sa de viață (atribuire unică sau multiplă) și cerințele de siguranță a firelor de execuție (acces mono sau multi-thread).

Când se aplică inițializarea întârziată

Primul și cel mai frecvent scenariu — Dependency Injection. Framework-ul (Dagger, Hilt, Koin) injectează dependențele după crearea obiectului, prin urmare proprietatea nu poate fi inițializată în constructor. Fără lateinit ar trebui să declarăm toate dependențele ca nullable și să le verificăm la fiecare utilizare.

Al doilea scenariu — resurse grele: baza de date, client de rețea, manager de fișiere. Crearea lor necesită timp și memorie, de aceea ar trebui să fie inițializate doar la utilizarea efectivă. lazy este ideal pentru astfel de cazuri, garantând crearea unică.

A treia situație — componente Android (Activity, Fragment, ViewModel), al căror ciclu de viață este gestionat de sistemul de operare. Proprietățile care depind de onCreate, onViewCreated sau blocul init al ViewModel nu pot fi inițializate în constructor.

lateinit: mecanism și limitări

lateinit — este un modificator pentru proprietăți var care permite compilatorului Kotlin să amâne inițializarea. Compilatorul nu cere atribuirea valorii în constructor, dar generează o verificare la runtime la fiecare acces: dacă proprietatea nu este inițializată, se aruncă UninitializedPropertyAccessException.

kotlin
class MainActivity {
    lateinit var binding: ActivityMainBinding

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

Limitări ale lateinit: proprietatea trebuie declarată ca var (nu val), non-nullable, de tip non-primitiv (Int, Double, Boolean etc.). Motivul — tipurile primitive se compilează în primitive JVM, care nu au starea «neinițializat». Pentru proprietățile nullable inițializarea întârziată nu este necesară: null înseamnă deja absența valorii.

Pentru verificarea stării proprietății lateinit se folosește referința încorporată prin operatorul ::: ::propertyName.isInitialized. Aceasta este singura modalitate sigură de a verifica dacă proprietatea este inițializată, fără riscul de a primi o excepție. Verificarea este disponibilă doar din aceeași clasă sau dintr-o clasă internă, nu din cod extern.

kotlin
class LoginFragment {
    lateinit var binding: FragmentLoginBinding

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

Performanța lateinit

lateinit nu adaugă costuri suplimentare după inițializare: după atribuirea valorii, accesul la proprietate este identic cu referința directă la câmp. Singurul cost — verificarea inițializării la fiecare citire înainte de atribuire. După inițializare, compilatorul JIT optimizează verificarea.

O caracteristică importantă: proprietățile lateinit nu pot fi folosite în clase inline și nu sunt suportate pentru proprietăți cu getter/setter personalizate. Dacă proprietatea necesită acces calculat — folosiți lazy în loc de lateinit.

lazy: mecanism și avantaje

lazy — este un delegat de proprietate, încorporat în biblioteca standard Kotlin. Acesta calculează valoarea la prima accesare a proprietății și cache-uiește rezultatul pentru toate apelurile ulterioare. Spre deosebire de lateinit, lazy funcționează doar cu val, făcând proprietatea imutabilă după inițializare.

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 acceptă un parametru opțional LazyThreadSafetyMode, care controlează mecanismul de siguranță a firelor de execuție. Implicit se folosește SYNCHRONIZED — verificare dublă cu blocare (Double-Checked Locking), garantând inițializarea unică chiar și la accesul simultan din mai multe fire.

Moduri de siguranță a firelor de execuție în lazy

Modul PUBLICATION permite inițializarea paralelă: mai multe fire pot executa simultan blocul de inițializare, dar rezultatul este acceptat doar de la primul care finalizează. Este mai rapid decât SYNCHRONIZED la concurență ridicată, dar consumă mai multe resurse.

Modul NONE dezactivează complet sincronizarea. Folosiți-l doar pentru proprietăți al căror acces are loc garantat dintr-un singur fir. În acest mod, lazy funcționează cu costuri minime — practic ca o atribuire directă.

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

Când lazy este preferabil

lazy este alegerea corectă pentru dependențele inițializate o singură dată: repository-uri, clienți de rețea, cache-uri, baze de date. Semantica val protejează împotriva suprascrierii accidentale, iar siguranța implicită a firelor face codul sigur în medii multi-thread. lazy funcționează corect și cu tipuri primitive, ceea ce este imposibil cu lateinit.

În Android, lazy este adesea folosit pentru inițializarea dependențelor ViewModel prin by viewModels() sau pentru crearea clienților Retrofit. Totuși, fiți atenți: dacă blocul lazy capturează o referință către Activity sau Fragment, aceasta poate duce la scurgeri de memorie, deoarece delegatul păstrează închiderea până la sfârșitul vieții proprietății.

lateinit vs lazy: compararea abordărilor

Alegerea între lateinit și lazy — nu este o chestiune de preferință, ci o decizie arhitecturală determinată de natura proprietății. Fiecare mecanism își rezolvă sarcina, iar domeniile lor de aplicare se suprapun doar parțial.

Criteriulateinitlazy
Tipul proprietățiidoar vardoar val
Nullableinterzispermis
Tipuri primitiveinterzisepermise
Siguranța firelornu este garantatăSYNCHRONIZED implicit
Verificarea stării::x.isInitializednu este necesară
Excepție la eroareUninitializedPropertyAccessExceptioneroare în blocul de inițializare
Cachenu se aplicăcalcul unic
Android BindingView Binding, Data Bindingnu se folosește
Frameworkuri DIDagger, Hilt, Koininjectare manuală

Folosiți lateinit atunci când proprietatea trebuie să se schimbe după inițializare sau crearea sa este gestionată de cod extern. Exemplul tipic — View Binding în Android Activity: binding este creat în onCreate, dar rămâne var, deoarece framework-ul nu suportă val pentru acest scenariu.

Folosiți lazy atunci când proprietatea este inițializată o singură dată, calculul său este costisitor și valoarea nu se schimbă pe durata vieții obiectului. Exemplul clasic — crearea lentă a clientului Retrofit sau a bazei de date Room la prima accesare a repository-ului.

Combinarea lateinit și lazy

Într-o singură clasă pot fi folosite simultan ambele mecanisme. De exemplu, lateinit pentru View Binding și lazy pentru repository. Aceasta este o practică normală, reflectând cerințe diferite pentru proprietăți diferite. Principalul — nu confundați semantica: nu folosiți lateinit acolo unde este necesar val și nu folosiți lazy pentru proprietăți care trebuie suprascrise.

Erori tipice la utilizarea lateinit și lazy

Cea mai frecventă eroare cu lateinit — accesarea proprietății înainte de inițializarea acesteia. Aceasta duce la UninitializedPropertyAccessException, care nu este prins în faza de compilare, deoarece Kotlin are încredere în dezvoltator pentru succesiunea corectă a inițializării. Soluția — verificați întotdeauna starea prin ::property.isInitialized înainte de acces în situații ambigue.

A doua problemă frecventă — utilizarea lateinit pentru proprietăți care semantic sunt val. Dacă valoarea este setată o dată și nu se mai schimbă, lazy este alegerea mai corectă. Face proprietatea imutabilă, exclude suprascrierea accidentală și adaugă siguranța firelor gratuit.

A treia eroare — lazy cu efecte secundare. Blocul de inițializare lazy nu ar trebui să modifice starea externă sau să se bazeze pe ordinea de inițializare a altor proprietăți lazy, deoarece succesiunea calculelor depinde de prima accesare și poate fi neevidentă. Dacă proprietățile lazy se referă una la alta, aceasta duce la o dependență ciclică și StackOverflowError.

A patra problemă — scurgere de memorie prin lazy în Android. Dacă blocul lazy capturează o referință către Activity sau Fragment — delegatul păstrează închiderea, iar colectorul de gunoi nu poate elibera componenta chiar și după distrugerea acesteia. Soluția — folosiți lazy doar cu obiecte cu viață scurtă sau transmiteți contextul Application, nu Activity.

A cincea eroare tipică — încercarea de a aplica lateinit tipurilor primitive. Compilatorul Kotlin blochează aceasta la nivel de sintaxă, dar dezvoltatorii încearcă să ocolească limitarea prin intermediul wrapper-elor nullable. Aceasta duce la verificări suplimentare de null și anulează complet avantajele inițializării întârziate.

Întrebări frecvente

Care este diferența dintre lateinit și lazy în Kotlin?

lateinit — modificator pentru proprietăți var, care permite inițializarea după constructor. lazy — delegat pentru proprietăți val, care calculează valoarea la prima accesare și o cache-uiește. lateinit nu suportă tipuri primitive și nullable, iar lazy este implicit thread-safe.

Se poate verifica dacă o proprietate lateinit este inițializată?

Da, prin referința încorporată la proprietate: ::propertyName.isInitialized. Metoda returnează true dacă proprietatea este deja inițializată. Aceasta este singura modalitate sigură de a evita UninitializedPropertyAccessException la lucrul cu câmpuri lateinit.

De ce nu poate fi folosit lateinit cu tipuri primitive?

Tipurile primitive — Int, Double, Boolean și altele — se compilează în primitive JVM (int, double, boolean) care nu au starea «neinițializat». lateinit folosește null ca un indicator, iar primitivele nu pot fi null, prin urmare mecanismul este fizic nerealizabil pentru aceste tipuri.

Care este modul implicit de siguranță a firelor pentru lazy?

Implicit se folosește LazyThreadSafetyMode.SYNCHRONIZED — verificare dublă cu blocare, garantând inițializarea unică la accesul din mai multe fire. Pentru scenarii single-thread folosiți NONE, pentru concurență ridicată — PUBLICATION.

Când să folosiți lateinit în loc de lazy în Android?

Când proprietatea trebuie să se schimbe după inițializare sau crearea sa este gestionată de framework. Exemplul tipic — View Binding în Android Activity: binding este creat în onCreate și trebuie să fie var. Pentru dependențe val inițializate o singură dată folosiți lazy.

Rezumat

  • lateinit — modificator pentru proprietăți var în Kotlin, care permite inițializarea după constructor fără nullable
  • lazy — delegat de proprietate pentru val cu calcul unic și cache automat al rezultatului
  • lateinit aruncă UninitializedPropertyAccessException la accesul înainte de inițializare
  • lazy este implicit thread-safe prin LazyThreadSafetyMode.SYNCHRONIZED
  • lateinit este incompatibil cu tipurile primitive și proprietățile nullable
  • lazy poate cauza scurgeri de memorie în Android la capturarea contextului în închidere
  • Alegeți lateinit pentru proprietăți mutabile și lazy pentru dependențe val inițializate o singură dată

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și