Geç başlatma (lazy initialization), Kotlin’de bir nesne özelliğinin oluşturulma anında değil, ilk erişimde başlatıldığı bir mekanizmadır. JetBrains, 2024’e göre, lateinit ve lazy bu stratejiyi uygulamak için iki yerleşik araçtır. Her ikisi de geç başlatma sorununu çözer, ancak çalışma mekanizması ve uygulama kapsamı açısından temelde farklılık gösterir.
Anahtar Noktalar
Geç başlatma, bir sınıf özelliğinin değerini nesne oluşturma anında değil, daha sonra ihtiyaç duyulduğunda aldığı bir desendir. Kotlin’de bu desen iki temelde farklı şekilde uygulanır: lateinit değiştiricisi ve lazy temsilcisi.
Her iki mekanizma da ortak bir sorunu çözer — bir özellik sınıfta var olmalıdır, ancak değeri ya nesne oluşturma anında bilinmemektedir ya da hesaplaması gereksiz yere yapmak için çok kaynak yoğundur. Google I/O 2023’e göre, tipik bir Android uygulamasındaki özelliklerin %40’a kadarı geç başlatma yoluyla optimize edilebilir ve başlangıç süresini %15–25 oranında azaltır.
lateinit ve lazy arasındaki seçim üç faktör tarafından belirlenir: özelliğin değişebilirliği (var veya val), yaşam döngüsü (tek veya çoklu atama) ve iş parçacığı güvenliği gereksinimleri (tek iş parçacıklı veya çok iş parçacıklı erişim).
İlk ve en yaygın senaryo Bağımlılık Enjeksiyonudur. Framework (Dagger, Hilt, Koin) bağımlılıkları nesne oluşturulduktan sonra enjekte eder, bu nedenle özellik kurucuda başlatılamaz. lateinit olmadan, tüm bağımlılıklar nullable olarak bildirilmeli ve her kullanımda kontrol edilmelidir.
İkinci senaryo ağır kaynaklardır: veritabanları, ağ istemcileri, dosya yöneticileri. Bunların oluşturulması zaman ve bellek gerektirir, bu nedenle yalnızca gerçekten kullanıldıklarında başlatılmalıdırlar. lazy bu gibi durumlar için idealdir ve tek seferlik oluşturmayı garanti eder.
Üçüncü durum, yaşam döngüsü işletim sistemi tarafından yönetilen Android bileşenleridir (Activity, Fragment, ViewModel). onCreate, onViewCreated veya ViewModel’in init bloğuna bağlı olan özellikler kurucuda başlatılamaz.
lateinit, Kotlin derleyicisinin başlatmayı ertelemesine izin veren var özellikleri için bir değiştiricidir. Derleyici kurucuda değer ataması gerektirmez, ancak her erişimde bir çalışma zamanı kontrolü oluşturur: özellik başlatılmamışsa, UninitializedPropertyAccessException fırlatır.
class MainActivity {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
lateinit sınırlamaları: özellik var (val değil), boş olmayan ve ilkel türde (Int, Double, Boolean vb.) olmayan olarak bildirilmelidir. Nedeni, ilkel türlerin JVM ilkellerine derlenmesi ve “başlatılmamış” durumunun olmamasıdır. Boş değer alabilen özellikler için geç başlatma gerekli değildir: null zaten değerin olmadığı anlamına gelir.
lateinit özelliğinin durumunu kontrol etmek için, :: operatörü aracılığıyla yerleşik referansı kullanın: ::propertyName.isInitialized. Bu, bir istisna riski olmadan bir özelliğin başlatılıp başlatılmadığını kontrol etmenin tek güvenli yoludur. Kontrol yalnızca aynı sınıftan veya iç sınıftan kullanılabilir, harici koddan kullanılamaz.
class LoginFragment {
lateinit var binding: FragmentLoginBinding
fun isReady(): Boolean {
return ::binding.isInitialized
}
}
lateinit başlatmadan sonra ek yük getirmez: değer atandıktan sonra, özellik erişimi doğrudan alan erişimiyle aynıdır. Tek maliyet, atamadan önce her okumada başlatma kontrolüdür. Başlatmadan sonra, JIT derleyicisi kontrolü optimize eder.
Önemli bir not: lateinit özellikleri satır içi sınıflarda kullanılamaz ve özel getter/setter’a sahip özellikler için desteklenmez. Bir özellik hesaplanmış erişim gerektiriyorsa, lateinit yerine lazy kullanın.
lazy, Kotlin standart kütüphanesine yerleşik bir özellik temsilcisidir. Özelliğe ilk erişimde değeri hesaplar ve sonraki tüm çağrılar için sonucu önbelleğe alır. lateinit’in aksine, lazy yalnızca val ile çalışır ve başlatmadan sonra özelliği değişmez hale getirir.
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, iş parçacığı güvenliği mekanizmasını kontrol eden isteğe bağlı bir LazyThreadSafetyMode parametresi alır. Varsayılan SYNCHRONIZED’dır — kilitlemeli çift kontrol, birden çok iş parçacığından eşzamanlı erişimde bile tek başlatmayı garanti eder.
PUBLICATION modu paralel başlatmaya izin verir: birden çok iş parçacığı başlatma bloğunu aynı anda yürütebilir, ancak sonuç yalnızca ilk tamamlayan iş parçacığından kabul edilir. Yüksek rekabet durumunda SYNCHRONIZED’dan daha hızlıdır, ancak kaynak tüketimini artırır.
NONE modu senkronizasyonu tamamen devre dışı bırakır. Yalnızca erişimi tek bir iş parçacığından garanti edilen özellikler için kullanın. Bu modda lazy minimum ek yük ile çalışır — neredeyse doğrudan atama gibi.
val heavyConfig: Config by lazy(LazyThreadSafetyMode.NONE) {
Config.loadFromFile("config.json")
}
lazy bir kez başlatılan bağımlılıklar için doğru seçimdir: depolar, ağ istemcileri, önbellekler, veritabanları. val semantiği yanlışlıkla üzerine yazmaya karşı korur ve varsayılan iş parçacığı güvenliği, kodu çok iş parçacıklı ortamlarda güvenli hale getirir. lazy ayrıca ilkel türlerle de doğru şekilde çalışır, bu lateinit ile imkansızdır.
Android’de, lazy genellikle by viewModels() aracılığıyla ViewModel bağımlılıklarını başlatmak veya Retrofit istemcileri oluşturmak için kullanılır. Ancak dikkatli olun: bir lazy bloğu bir Activity veya Fragment’e referans yakalarsa, bellek sızıntısına neden olabilir, çünkü temsilci, özelliğin ömrü boyunca kapanımı tutar.
lateinit ve lazy arasındaki seçim bir tercih meselesi değil, özelliğin doğası tarafından belirlenen mimari bir karardır. Her mekanizma kendi görevini çözer ve uygulama alanları yalnızca kısmen örtüşür.
| Kriter | lateinit | lazy |
|---|---|---|
| Özellik türü | yalnızca var | yalnızca val |
| Nullable | izin verilmez | izin verilir |
| İlkel türler | izin verilmez | izin verilir |
| İş parçacığı güvenliği | garanti edilmez | varsayılan olarak SYNCHRONIZED |
| Durum kontrolü | ::x.isInitialized | gerekmez |
| Hata durumunda istisna | UninitializedPropertyAccessException | init bloğunda hata |
| Önbellekleme | uygulanmaz | tek hesaplama |
| Android Binding | View Binding, Data Binding | kullanılmaz |
| DI frameworkleri | Dagger, Hilt, Koin | manuel enjeksiyon |
lateinit’i, bir özelliğin başlatmadan sonra değişmesi gerektiğinde veya oluşturulması harici kod tarafından yönetildiğinde kullanın. Tipik bir örnek, Android Activity’de View Binding’dir: binding onCreate’te oluşturulur ancak var olarak kalır çünkü framework bu senaryo için val’i desteklemez.
lazy’yi, bir özellik bir kez başlatıldığında, hesaplaması pahalı olduğunda ve değer nesnenin ömrü boyunca değişmediğinde kullanın. Klasik bir örnek, depoya ilk erişimde bir Retrofit istemcisinin veya Room veritabanının tembel oluşturulmasıdır.
Her iki mekanizma da aynı sınıf içinde aynı anda kullanılabilir. Örneğin, View Binding için lateinit ve bir depo için lazy. Bu, farklı özellikler için farklı gereksinimleri yansıtan normal bir uygulamadır. Önemli olan semantiği karıştırmamaktır: val gereken yerde lateinit kullanmayın ve yeniden atanması gereken özellikler için lazy kullanmayın.
lateinit ile en yaygın hata, özelliğe başlatılmadan önce erişmektir. Bu, UninitializedPropertyAccessException’a yol açar ve derleme zamanında yakalanmaz çünkü Kotlin, geliştiricinin doğru başlatma sırasını sağlayacağına güvenir. Çözüm, belirsiz durumlarda erişimden önce ::property.isInitialized aracılığıyla her zaman durumu kontrol etmektir.
İkinci yaygın sorun, anlamsal olarak val olan özellikler için lateinit kullanmaktır. Değer bir kez ayarlanıp hiç değişmiyorsa, lazy daha doğru seçimdir. Özelliği değişmez yapar, yanlışlıkla üzerine yazmayı önler ve ücretsiz olarak iş parçacığı güvenliği ekler.
Üçüncü hata, yan etkileri olan lazydir. lazy başlatma bloğu, harici durumu değiştirmemeli veya diğer lazy özelliklerinin başlatma sırasına bağlı olmamalıdır, çünkü hesaplama sırası ilk erişime bağlıdır ve açık olmayabilir. lazy özellikleri birbirine referans verirse, bu döngüsel bağımlılığa ve StackOverflowError’a yol açar.
Dördüncü sorun, Android’de lazy yoluyla bellek sızıntılarıdır. Bir lazy bloğu bir Activity veya Fragment’e referans yakalarsa, temsilci kapanımı tutar ve çöp toplayıcı, bileşeni yok edildikten sonra bile serbest bırakamaz. Çözüm, lazy’yi yalnızca kısa ömürlü nesnelerle kullanmak veya Activity yerine Application bağlamını iletmektir.
Beşinci tipik hata, ilkel türlere lateinit uygulamaya çalışmaktır. Kotlin derleyicisi bunu sözdizimi düzeyinde engeller, ancak geliştiriciler nullable sarmalayıcılar aracılığıyla sınırlamayı aşmaya çalışır. Bu, gereksiz null kontrollerine yol açar ve geç başlatmanın faydalarını tamamen ortadan kaldırır.
Sıkça Sorulan Sorular
lateinit, var özellikleri için bir değiştiricidir ve kurucudan sonra başlatmaya izin verir. lazy, val özellikleri için bir temsilcidir, ilk erişimde değeri hesaplar ve önbelleğe alır. lateinit ilkel türleri ve nullable’ı desteklemezken, lazy varsayılan olarak iş parçacığı güvenlidir.
Evet, yerleşik özellik referansı aracılığıyla: ::propertyName.isInitialized. Metod, özellik zaten başlatılmışsa true döndürür. lateinit alanlarıyla çalışırken UninitializedPropertyAccessException’ı önlemenin tek güvenli yolu budur.
İlkel türler — Int, Double, Boolean ve diğerleri — JVM ilkellerine (int, double, boolean) derlenir ve “başlatılmamış” durumları yoktur. lateinit null‑ı bir işaret olarak kullanır ve ilkeller null olamaz, bu nedenle mekanizma bu türler için fiziksel olarak imkansızdır.
Varsayılan LazyThreadSafetyMode.SYNCHRONIZED’dır — kilitlemeli çift kontrol, birden çok iş parçacığından eşzamanlı erişim altında tek başlatmayı garanti eder. Tek iş parçacıklı senaryolar için NONE, yüksek rekabet için PUBLICATION kullanın.
Özellik başlatmadan sonra değişmesi gerektiğinde veya oluşturulması framework tarafından yönetildiğinde. Tipik bir örnek, Android Activity’deki View Binding’dir: binding onCreate’te oluşturulur ve var olmalıdır. Bir kez başlatılan val bağımlılıkları için lazy kullanın.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun