Kotlin - “Lateinit” ile “tembel” kullanarak mülk başlatma


280

Kotlin'de yapıcı içinde veya sınıf gövdesinin üstünde bir sınıf özelliği başlatmak istemiyorsanız, temel olarak şu iki seçeneğe sahipsiniz (dil başvurusundan):

  1. Tembel Başlatma

tembel (), bir lambda alan ve tembel bir mülkün uygulanması için temsilci olarak işlev görebilen bir Lazy örneğini döndüren bir işlevdir: get () için ilk çağrı, tembel () öğesine geçirilen lambda'yı yürütür ve sonucu hatırlar, sonraki çağrılar get () sadece hatırlanan sonucu döndürmek.

Misal

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

O kadar nerede olursa olsun ilk çağrıdan ve subquential çağrılar, Yani myLazyString dönecektir "Merhaba"

  1. Geç Başlatma

Normalde, boş olmayan tür olarak bildirilen özelliklerin yapıcıda başlatılması gerekir. Ancak, oldukça sık olarak bu uygun değildir. Örneğin, özellikler bağımlılık enjeksiyonu yoluyla veya bir birim testinin kurulum yönteminde başlatılabilir. Bu durumda, yapıcıda null olmayan bir başlatıcı sağlayamazsınız, ancak bir sınıfın gövdesindeki özelliğe başvururken yine de null denetimlerden kaçınmak istersiniz.

Bu durumu işlemek için özelliği lateinit değiştiricisiyle işaretleyebilirsiniz:

public class MyTest {
   
   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

Değiştirici yalnızca bir sınıfın gövdesinde bildirilen var özelliklerinde (birincil kurucuda değil) ve yalnızca özelliğin özel alıcı veya ayarlayıcıya sahip olmadığında kullanılabilir. Özelliğin türü boş olmamalı ve ilkel bir tür olmamalıdır.

Peki, bu iki seçenek arasında doğru seçim nasıl yapılır, çünkü ikisi de aynı sorunu çözebilir?

Yanıtlar:


334

İşte lateinit varve by lazy { ... }temsil edilen mülk arasındaki önemli farklar :

  • lazy { ... }delege yalnızca valözellikler için kullanılabilir , ancak lateinityalnızca varbir finalalana uygulanabilir , çünkü bir alana derlenemez , dolayısıyla hiçbir değişmezlik garanti edilemez;

  • lateinit vardeğeri depolayan ve değerin by lazy { ... }hesaplandıktan sonra depolandığı bir temsilci nesnesi oluşturan, temsilci örneğine başvuruyu sınıf nesnesindeki depolayan ve temsilci örneğiyle çalışan özellik için alıcıyı oluşturan bir destek alanı vardır . Sınıfta bulunan destek alanına ihtiyacınız varsa lateinit;

  • S'ye ek olarak val, lateinitnull olmayan özellikler ve Java ilkel türleri nulliçin kullanılamaz (bunun nedeni, başlatılmamış değer için kullanılmasıdır);

  • lateinit varörneğin bir çerçeve kodunun içinden nesnenin görüldüğü her yerden başlatılabilir ve tek bir sınıfın farklı nesneleri için birden çok başlatma senaryosu mümkündür. by lazy { ... }, sırayla, yalnızca bir alt sınıftaki özelliğin geçersiz kılınmasıyla değiştirilebilen özellik için tek başlatıcıyı tanımlar. Mülkünüzün muhtemelen önceden bilinmeyen bir şekilde dışarıdan başlatılmasını istiyorsanız kullanın lateinit.

  • Başlatma by lazy { ... }, varsayılan olarak iş parçacığı için güvenlidir ve başlatıcıyı en fazla bir kez çağırmayı garanti eder (ancak bu, başka bir lazyaşırı yük kullanılarak değiştirilebilir ). Bu durumda lateinit var, özelliği çok iş parçacıklı ortamlarda doğru şekilde başlatmak kullanıcının koduna bağlıdır.

  • Bir Lazyörnek kaydedilebilir, aktarılabilir ve hatta birden fazla özellik için kullanılabilir. Aksine, lateinit vars herhangi bir ek çalışma zamanı durumunu depolamaz (yalnızca nullbaşlatılmamış değer alanında).

  • Eğer bir örneğine referans tutun Lazy, isInitialized()bunun ilklendirilip olmadığını kontrol etmek için (ve edebilirsiniz sağlar yansıması ile bu örneği elde etmek bir temsilci özelliğinden). Bir lateinit özelliğinin başlatılıp başlatılmadığını kontrol etmek için Kotlin 1.2'den beri kullanabilirsinizproperty::isInitialized .

  • Geçilen bir lambda by lazy { ... }, kapatıldığı yerde kullanılan bağlamdan referansları yakalayabilir . Daha sonra referansları saklar ve sadece özellik başlatıldıktan sonra serbest bırakır. Bu, Android etkinlikleri gibi nesne hiyerarşilerine yol açabilir, çok uzun süre serbest bırakılmaz (veya mülk erişilebilir kalırsa ve asla erişilmezse), bu nedenle başlatıcı lambda içinde ne kullandığınıza dikkat etmelisiniz.

Ayrıca, soruda belirtilmeyen başka bir yol daha vardır: Delegates.notNull()Java ilkel türler de dahil olmak üzere null olmayan özelliklerin ertelenmiş başlatılması için uygundur.


9
Mükemmel cevap! Ben lateinitözelliği Kotlin ve Java erişim yolları farklı böylece destek alanı ayarlayıcı görünürlüğü ile ortaya ekler . Ve Java kodundan bu özellik nullKotlin'de herhangi bir kontrol yapılmadan bile ayarlanabilir . Bu nedenle lateinittembel başlatma için değil, Kotlin kodundan başlatma için değil.
Michael

Swift'in "!" Eşdeğeri bir şey var mı? ?? Başka bir deyişle, bu, ilk kullanıma hazır hale getirilmiş, ancak başarısız olmadan null olup olmadığı için denetlenebilir. 'TheObject == null' seçeneğini işaretlerseniz Kotlin'in 'lateinit' hatası "lateinit property currentUser başlatılmadı" ile başarısız olur. Çekirdek kullanım senaryosunda boş olmayan bir nesneye sahip olduğunuzda (ve bu nedenle boş olmayan bir soyutlamaya karşı kodlamak istediğinizde), ancak istisnai / sınırlı senaryolarda boş olan (yani: şu anda oturum açmış olana erişme) İlk giriş / giriş ekranında hariç hiçbir zaman boş olmayan kullanıcı)
Marchy

@Marchy, bunu yapmak için açıkça depolanmış Lazy+ .isInitialized()kullanabilirsiniz. Sanırım nullondan alamayacağınız garanti nedeniyle böyle bir özelliği kontrol etmek için basit bir yolu yoktur null. :) Bu demoya bakın .
kısayol tuşu

@hotkey Çok fazla kullanmanın by lazyoluşturma süresini veya çalışma zamanını yavaşlatabileceğine dair herhangi bir nokta var mı ?
Dr.jacky

Başlatılmamış değer lateinitkullanımını önlemek için kullanma fikrini beğendim null. Bunun dışında nullasla kullanılmamalıdır ve lateinitnull ile ortadan kaldırılabilir.
Kotlin'i

26

Ek olarak hotkey, iyi cevaba ek olarak , pratikte ikisi arasında nasıl seçim yapacağım:

lateinit harici başlatma içindir: bir yöntemi çağırarak değerinizi başlatmak için harici öğelere ihtiyacınız olduğunda.

örneğin arayarak:

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

İken lazysadece sizin nesneye iç bağımlılıkları kullandığında olduğunu.


1
Harici bir nesneye bağlı olsa bile hala tembel başlayabileceğimizi düşünüyorum. Değeri bir iç değişkene geçirmeniz yeterlidir. Ve tembel başlatma sırasında dahili değişkeni kullanın. Ama Lateinit kadar doğal.
Elye,

Bu yaklaşım UninitializedPropertyAccessException atar, ben değeri kullanmadan önce bir ayarlayıcı işlev çağırıyorum çift kontrol etti. Lateinit ile özlediğim belirli bir kural var mı? Cevabınızda MyClass ve Any'u android Context ile değiştirin, bu benim durumum.
Talha

24

Çok Kısa ve özlü Cevap

lateinit: Son zamanlarda null olmayan özellikleri başlatır

Tembel başlatmanın aksine, lateinit derleyicinin null olmayan özelliğin değerinin normal derlemek için yapıcı aşamasında saklanmadığını fark etmesine izin verir.

tembel başlatma

Tembel tarafından , Kotlin'de tembel başlatma yapan salt okunur (val) özellikler uygulanırken çok yararlı olabilir .

by lazy {...}, tanımlayıcısının değil, tanımlanan özelliğin ilk kullanıldığı yerde başlatıcısını gerçekleştirir.


büyük cevap, özellikle "kendi tanımlayıcı değil, tanımlanan özellik ilk kullanıldığı yerde başlatıcısı gerçekleştirir"
user1489829

17

latife vs tembel

  1. lateinit

    i) Değişken değişkeni ile kullan [var]

    lateinit var name: String       //Allowed
    lateinit val name: String       //Not Allowed

    ii) Yalnızca boş olmayan veri türleriyle izin verilir

    lateinit var name: String       //Allowed
    lateinit var name: String?      //Not Allowed

    iii) Değerin gelecekte başlatılacağı derleyiciye bir vaattir.

Not : lateinit değişkeni başlatmadan erişmeye çalışırsanız, UnInitializedPropertyAccessException atar.

  1. tembel

    i) Tembel başlatma, nesnelerin gereksiz başlatılmasını önlemek için tasarlanmıştır.

    ii) Değişkeniniz kullanılmadıkça başlatılmaz.

    iii) Sadece bir kez başlatılır. Bir dahaki sefere kullandığınızda, değeri önbellekten alırsınız.

    iv) İş parçacığı için güvenlidir (İlk kez kullanıldığı iş parçacığında başlatılır. Diğer iş parçacıkları önbellekte depolanan değeri kullanır).

    v) Değişken yalnızca val olabilir .

    vi) Değişken yalnızca sıfırlanamaz .


7
Tembel değişkente var olamaz düşünüyorum.
Däñish Shärmà

4

Tüm harika cevaplara ek olarak, tembel yükleme adı verilen bir kavram var:

Tembel yükleme, bir nesnenin başlatılmasını gereken noktaya kadar ertelemek için bilgisayar programlamasında yaygın olarak kullanılan bir tasarım modelidir.

Düzgün kullanarak, uygulamanızın yüklenme süresini azaltabilirsiniz. Ve Kotlin'in uygulama şekli lazy(), ihtiyaç duyduğunuzda değişkeninize gereken değeri yükler.

Ancak lateinit, bir değişkenin boş veya boş olmayacağından ve -eg için yöntemde -eg kullanmadan önce başlatılacağından emin olduğunuzda kullanılır onResume()ve bu nedenle onu null olabilecek bir tür olarak bildirmek istemezsiniz.


Evet, ben de başlatılmış onCreateView, onResumeve diğer lateinit, ama bazen hata (bazı olaylar daha önce başlamış olduğundan) oluştu. Yani belki by lazyuygun bir sonuç verebilir. lateinitYaşam döngüsü sırasında değişebilen null olmayan değişkenler için kullanıyorum .
CoolMind

2

Yukarıda her şey doğrudur, ancak gerçeklerden biri basit bir açıklama LAZY ---- Nesnenin bir örneğinin oluşturulmasını ilk kullanımına kadar ertelemek istediğiniz durumlar vardır. Bu teknik, tembel başlatma veya tembel örnekleme olarak bilinir. Tembel başlatmanın temel amacı performansı artırmak ve bellek ayak izinizi azaltmaktır. Türünüzün bir örneğinin başlatılması büyük bir hesaplama maliyetine sahipse ve program gerçekten kullanmıyorsa, CPU döngülerini geciktirmek veya hatta boşa harcamaktan kaçınmak istersiniz.


0

Spring konteyneri kullanıyorsanız ve boş olmayan fasulye alanını başlatmak istiyorsanız lateinit, daha uygundur.

    @Autowired
    lateinit var myBean: MyBean

1
gibi olmalı@Autowired lateinit var myBean: MyBean
Cnfn

0

Değiştirilemez bir değişken kullanıyorsanız, by lazy { ... }veya ile başlatmak daha iyidir val. Bu durumda, gerektiğinde ve en fazla 1 kez başlatılacağından emin olabilirsiniz.

Boş olmayan bir değişken istiyorsanız, bu onun değerini değiştirebilir, kullanın lateinit var. Android gelişmede daha sonra gibi böyle olaylar bunu başlatabilir onCreate, onResume. REST isteğini çağırır ve bu değişkene erişirseniz, bu değişkenin UninitializedPropertyAccessException: lateinit property yourVariable has not been initializedbaşlatılabileceğinden daha hızlı çalışabileceğinden, bir istisnaya neden olabileceğini unutmayın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.