lateinit / lazy: суштина одложене иницијализације и механизми у Kotlin-у

Аутор: IT Sectr Објављено: 2026-06-23 Време читања: 8 мин

Одложена иницијализација (lazy initialization) — механизам у Kotlin-у при којем се својство објекта иницијализује не у тренутку креирања, већ при првом приступу њему. Према подацима JetBrains, 2024, lateinit и lazy — два уграђена алата за реализацију ове стратегије. Оба решавају проблем одлагања иницијализације, али се принципијелно разликују по механизму рада и области примене.

Главно

  • lateinit — модификатор за var-својства, који дозвољава иницијализацију након креирања објекта
  • lazy — делегат за val-својства, који иницијализује вредност при првом приступу
  • lateinit захтева var и не подржава примитивне типове JVM-а
  • lazy је подразумевано безбедан за нити и кешира израчунати резултат
  • lateinit баца UninitializedPropertyAccessException при прераном приступу

Шта је одложена иницијализација у Kotlin-у?

Одложена иницијализација — то је образац при којем својство класе добија вредност не у тренутку конструисања објекта, већ касније, на захтев. У Kotlin-у овај образац је реализован на два принципијелно различита начина: модификатором lateinit и делегатом lazy.

Оба механизма решавају заједнички проблем — својство мора да постоји у класи, али његова вредност или још није позната у тренутку креирања објекта, или је њено израчунавање превише ресурсно захтевно за извођење без потребе. Према подацима Google I/O 2023, до 40% својстава у типичној Android апликацији може се оптимизовати кроз одложену иницијализацију, што смањује време покретања за 15–25%.

Избор између lateinit и lazy одређују три фактора: променљивост својства (var или val), време његовог живота (једнократно или вишекратно додељивање) и захтеви за безбедност нити (једнонитни или више nitni приступ).

Када се примењује одложена иницијализација

Први и најчешћи сценарио — Dependency Injection. Оквир (Dagger, Hilt, Koin) убризгава зависности након креирања објекта, стога својство не може бити иницијализовано у конструктору. Без lateinit-а морали бисмо да декларишемо све зависности као nullable и проверавамо их при сваком коришћењу.

Други сценарио — тешки ресурси: база података, мрежни клијент, менаџер датотека. Њихово креирање захтева време и меморију, стога би требало да се иницијализују само при стварном коришћењу. lazy је идеалан за такве случајеве, гарантујући једнократно креирање.

Трећа ситуација — Android компоненте (Activity, Fragment, ViewModel), чији животни циклус управља оперативни систем. Својства која зависе од onCreate, onViewCreated или init-блока ViewModel-а не могу бити иницијализована у конструктору.

lateinit: механизам и ограничења

lateinit — то је модификатор за var-својства који дозвољава Kotlin компајлеру да одложи иницијализацију. Компајлер не захтева додељивање вредности у конструктору, али генерише проверу у време извршавања при сваком приступу: ако својство није иницијализовано, баца се UninitializedPropertyAccessException.

kotlin
class MainActivity {
    lateinit var binding: ActivityMainBinding

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

Ограничења lateinit-а: својство мора бити декларисано као var (не val), не nullable, не примитивног типа (Int, Double, Boolean итд.). Разлог — примитивни типови се компајлирају у JVM примитиве, који немају стање «није иницијализовано». За nullable својства одложена иницијализација није потребна: null већ значи одсуство вредности.

За проверу стања lateinit својства користи се уграђена референца кроз оператор ::: ::propertyName.isInitialized. Ово је једини сигуран начин да се провери да ли је својство иницијализовано, без ризика од добијања изузетка. Провера је доступна само из исте класе или унутрашње класе, не из спољашњег кода.

kotlin
class LoginFragment {
    lateinit var binding: FragmentLoginBinding

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

Перформансе lateinit-а

lateinit не додаје додатне трошкове након иницијализације: након додељивања вредности, приступ својству је идентичан директном приступу пољу. Једини трошак — провера иницијализације при сваком читању пре додељивања. Након иницијализације, JIT компајлер оптимизује проверу.

Важна карактеристика: lateinit својства не могу се користити у inline класама и нису подржана за својства са прилагођеним getter/setter-ом. Ако својство захтева израчунати приступ — користите lazy уместо lateinit-а.

lazy: механизам и предности

lazy — то је делегат својства, уграђен у стандардну Kotlin библиотеку. Он израчунава вредност при првом приступу својству и кешира резултат за све наредне позиве. За разлику од lateinit-а, lazy ради само са val, чинећи својство непроменљивим након иницијализације.

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 прихвата опциони параметар LazyThreadSafetyMode, који управља механизмом безбедности нити. Подразумевано се користи SYNCHRONIZED — двострука провера са блокирањем (Double-Checked Locking), која гарантује једнократну иницијализацију чак и при истовременом приступу из више нити.

Режими безбедности нити lazy-ја

Режим PUBLICATION дозвољава паралелну иницијализацију: више нити могу истовремено да изврше блок иницијализације, али резултат ће бити прихваћен само од прве која заврши. Ово је брже од SYNCHRONIZED-а при високој конкуренцији, али повећава потрошњу ресурса.

Режим NONE потпуно искључује синхронизацију. Користите га само за својства чији се приступ гарантовано одвија из једне нити. У овом режиму lazy ради са минималним додатним трошковима — практично као директно додељивање.

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

Када је lazy пожељнији

lazy је прави избор за једнократно иницијализоване зависности: репозиторијуме, мрежне клијенте, кешеве, базе података. Семантика val штити од случајног преписивања, а подразумевана безбедност нити чини код безбедним у окружењу са више нити. lazy такође исправно ради са примитивним типовима, што је немогуће са lateinit-ом.

У Android-у lazy се често користи за иницијализацију ViewModel зависности преко by viewModels() или за креирање Retrofit клијената. Међутим, будите опрезни: ако lazy блок ухвати референцу на Activity или Fragment, то може довести до цурења меморије, јер делегат чува затварање до краја живота својства.

lateinit vs lazy: поређење приступа

Избор између lateinit и lazy — није питање преференције, већ архитектонска одлука одређена природом својства. Сваки механизам решава свој задатак, а њихове области примене се само делимично преклапају.

Критеријумlateinitlazy
Тип својствасамо varсамо val
Nullableзабрањендозвољен
Примитивни типовизабрањенидозвољени
Безбедност нитиније гарантованаSYNCHRONIZED подразумевано
Провера стања::x.isInitializedније потребна
Изузетак при грешциUninitializedPropertyAccessExceptionгрешка у блоку иницијализације
Кеширањене примењује сеједнократно израчунавање
Android BindingView Binding, Data Bindingне користи се
DI оквириDagger, Hilt, Koinручно убризгавање

Користите lateinit када својство треба да се мења након иницијализације или његово креирање управља спољашњим кодом. Типичан пример — View Binding у Android Activity: binding се креира у onCreate, али остаје var, јер оквир не подржава val за овај сценарио.

Користите lazy када се својство иницијализује једнократно, његово израчунавање је скупо и вредност се не мења током живота објекта. Класичан пример — лењо креирање Retrofit клијента или Room базе података при првом приступу репозиторијуму.

Комбиновање lateinit и lazy

У једној класи могу се истовремено користити оба механизма. На пример, lateinit за View Binding и lazy за репозиторијум. Ово је нормална пракса, која одражава различите захтеве за различита својства. Главно — не мешајте семантику: не користите lateinit тамо где је потребан val и не користите lazy за својства која треба да се преписују.

Типичне грешке при коришћењу lateinit и lazy

Најчешћа грешка са lateinit — приступ својству пре него што је иницијализовано. Ово доводи до UninitializedPropertyAccessException, који се не хвата у фази компајлирања, јер Kotlin верује програмеру у погледу исправне секвенце иницијализације. Решење — увек проверавајте стање кроз ::property.isInitialized пре приступа у нејасним ситуацијама.

Други чест проблем — коришћење lateinit за својства која су семантички val. Ако се вредност поставља једном и више се не мења, lazy је исправнији избор. Он чини својство непроменљивим, искључује случајно преписивање и додаје безбедност нити бесплатно.

Трећа грешка — lazy са споредним ефектима. Блок иницијализације lazy-ја не би требало да мења спољашње стање или да се ослања на редослед иницијализације других lazy својстава, јер секвенца израчунавања зависи од првог приступа и може бити неочигледна. Ако lazy својства упућују једна на другу, то доводи до цикличне зависности и StackOverflowError-а.

Четврти проблем — цурење меморије кроз lazy у Android-у. Ако lazy блок ухвати референцу на Activity или Fragment — делегат чува затварање, и сакупљач смећа не може да ослободи компоненту чак ни након њеног уништења. Решење — користите lazy само са краткотрајним објектима или проследите Application контекст, а не Activity.

Пета типична грешка — покушај примене lateinit на примитивне типове. Kotlin компајлер ово блокира на нивоу синтаксе, али програмери покушавају да заобиђу ограничење кроз nullable омотаче. Ово доводи до непотребних провера на null и потпуно неутралише предности одложене иницијализације.

Често постављана питања

Која је разлика између lateinit и lazy у Kotlin-у?

lateinit — модификатор за var-својства, који дозвољава иницијализацију након конструктора. lazy — делегат за val-својства, који израчунава вредност при првом приступу и кешира је. lateinit не подржава примитивне типове и nullable, а lazy је подразумевано безбедан за нити.

Може ли се проверити да ли је lateinit својство иницијализовано?

Да, кроз уграђену референцу на својство: ::propertyName.isInitialized. Метод враћа true ако је својство већ иницијализовано. Ово је једини сигуран начин да се избегне UninitializedPropertyAccessException при раду са lateinit пољима.

Зашто се lateinit не може користити са примитивним типовима?

Примитивни типови — Int, Double, Boolean и други — компајлирају се у JVM примитиве (int, double, boolean) који немају стање «није иницијализовано». lateinit користи null као ознаку, а примитиви не могу бити null, стога механизам физички није изводљив за ове типове.

Који је подразумевани режим безбедности нити за lazy?

Подразумевано се користи LazyThreadSafetyMode.SYNCHRONIZED — двострука провера са блокирањем, која гарантује једнократну иницијализацију при приступу из више нити. За једнонитне сценарије користите NONE, за високу конкуренцију — PUBLICATION.

Када користити lateinit уместо lazy у Android-у?

Када својство треба да се мења након иницијализације или његово креирање управља оквиром. Типичан пример — View Binding у Android Activity: binding се креира у onCreate и мора бити var. За једнократно иницијализоване val зависности користите lazy.

Резиме

  • lateinit — модификатор за var-својства у Kotlin-у, који омогућава иницијализацију након конструктора без nullable
  • lazy — делегат својства за val са једнократним израчунавањем и аутоматским кеширањем резултата
  • lateinit баца UninitializedPropertyAccessException при приступу пре иницијализације
  • lazy је подразумевано безбедан за нити кроз LazyThreadSafetyMode.SYNCHRONIZED
  • lateinit је некомпатибилан са примитивним типовима и nullable својствима
  • lazy може изазвати цурење меморије у Android-у при хватању контекста у затварању
  • Бирајте lateinit за променљива својства и lazy за једнократно иницијализоване val зависности

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође