Kotlin - Inisialisasi properti menggunakan "oleh malas" vs. "lateinit"

280

Di Kotlin jika Anda tidak ingin menginisialisasi properti kelas di dalam konstruktor atau di bagian atas badan kelas, Anda pada dasarnya memiliki dua opsi ini (dari referensi bahasa):

  1. Inisialisasi Malas

lazy () adalah fungsi yang mengambil lambda dan mengembalikan instance Malas yang dapat berfungsi sebagai delegasi untuk mengimplementasikan properti lazy: panggilan pertama untuk mendapatkan () mengeksekusi lambda yang dilewatkan ke lazy () dan mengingat hasilnya, panggilan selanjutnya untuk mendapatkan () cukup kembalikan hasil yang diingat.

Contoh

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

Jadi panggilan pertama dan panggilan subquential, di mana pun itu, ke myLazyString akan mengembalikan "Halo"

  1. Inisialisasi Terlambat

Biasanya, properti yang dideklarasikan memiliki tipe non-null harus diinisialisasi dalam konstruktor. Namun, cukup sering ini tidak nyaman. Misalnya, properti dapat diinisialisasi melalui injeksi dependensi, atau dalam metode pengaturan tes unit. Dalam hal ini, Anda tidak bisa menyediakan inisialisasi non-null di konstruktor, tetapi Anda masih ingin menghindari cek nol ketika mereferensikan properti di dalam tubuh kelas.

Untuk menangani kasus ini, Anda dapat menandai properti dengan pengubah lateinit:

public class MyTest {
   
   lateinit var subject: TestSubject

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

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

Pengubah hanya dapat digunakan pada properti var dideklarasikan di dalam tubuh kelas (tidak di konstruktor utama), dan hanya ketika properti tidak memiliki pengambil atau penyetel kustom. Jenis properti harus bukan nol, dan tidak boleh tipe primitif.

Jadi, bagaimana memilih dengan benar antara kedua opsi ini, karena keduanya dapat menyelesaikan masalah yang sama?

regmora
sumber

Jawaban:

335

Berikut adalah perbedaan signifikan antara lateinit vardan by lazy { ... }properti yang didelegasikan:

  • lazy { ... }mendelegasikan hanya dapat digunakan untuk valproperti, sedangkan lateinithanya dapat diterapkan ke vars, karena itu tidak dapat dikompilasi ke finallapangan, sehingga tidak ada kekekalan dapat dijamin;

  • lateinit varmemiliki bidang dukungan yang menyimpan nilai, dan by lazy { ... }membuat objek delegasi di mana nilai disimpan setelah dihitung, menyimpan referensi ke instance delegasi dalam objek kelas dan menghasilkan pengambil untuk properti yang bekerja dengan instance delegasi. Jadi, jika Anda membutuhkan bidang dukungan hadir di kelas, gunakan lateinit;

  • Selain vals, lateinittidak dapat digunakan untuk properti non-nullable dan tipe primitif Java (ini karena nulldigunakan untuk nilai yang tidak diinisialisasi);

  • lateinit vardapat diinisialisasi dari mana saja objek dilihat, misalnya dari dalam kode kerangka kerja, dan beberapa skenario inisialisasi dimungkinkan untuk objek yang berbeda dari satu kelas. by lazy { ... }, pada gilirannya, menentukan satu-satunya penginisialisasi untuk properti, yang hanya dapat diubah dengan mengesampingkan properti dalam subkelas. Jika Anda ingin properti Anda diinisialisasi dari luar dengan cara yang mungkin tidak diketahui sebelumnya, gunakan lateinit.

  • Inisialisasi by lazy { ... }adalah thread-safe secara default dan menjamin bahwa initializer dipanggil paling banyak sekali (tetapi ini dapat diubah dengan menggunakan overload lainlazy ). Dalam kasus lateinit var, tergantung pada kode pengguna untuk menginisialisasi properti dengan benar di lingkungan multi-utas.

  • Sebuah Lazyinstance dapat disimpan, diedarkan dan bahkan digunakan untuk beberapa properti. Sebaliknya, lateinit vars tidak menyimpan status runtime tambahan (hanya nulldi bidang untuk nilai yang tidak diinisialisasi).

  • Jika Anda memegang referensi ke sebuah instance dari Lazy, isInitialized()memungkinkan Anda untuk memeriksa apakah itu sudah diinisialisasi (dan Anda dapat memperoleh instance tersebut dengan refleksi dari properti yang didelegasikan). Untuk memeriksa apakah properti lateinit telah diinisialisasi, Anda dapat menggunakan property::isInitializedsejak Kotlin 1.2 .

  • Sebuah lambda yang diteruskan by lazy { ... }dapat menangkap referensi dari konteks di mana ia digunakan dalam penutupannya .. Kemudian akan menyimpan referensi dan melepaskannya hanya setelah properti diinisialisasi. Ini dapat menyebabkan hierarki objek, seperti aktivitas Android, tidak dirilis terlalu lama (atau pernah, jika properti tetap dapat diakses dan tidak pernah diakses), jadi Anda harus berhati-hati tentang apa yang Anda gunakan di dalam lambda penginisialisasi.

Juga, ada cara lain yang tidak disebutkan dalam pertanyaan:, Delegates.notNull()yang cocok untuk inisialisasi tangguhan properti non-null, termasuk tipe primitif Java.

hotkey
sumber
9
Jawaban bagus! Saya akan menambahkan bahwa lateinitmemperlihatkan bidang dukungan dengan visibilitas setter sehingga cara properti diakses dari Kotlin dan dari Jawa berbeda. Dan dari kode Java properti ini dapat disetel menjadi nulltanpa cek di Kotlin. Karena lateinititu bukan untuk inisialisasi malas tetapi untuk inisialisasi tidak harus dari kode Kotlin.
Michael
Apakah ada yang setara dengan Swift "!" ?? Dengan kata lain itu adalah sesuatu yang terlambat diinisialisasi tetapi BISA diperiksa untuk nol tanpa gagal. 'Lateinit' Kotlin gagal dengan "currentin properti properti lateinit belum diinisialisasi" jika Anda memeriksa 'theObject == null'. Ini sangat berguna ketika Anda memiliki objek yang tidak nol dalam skenario penggunaan intinya (dan karenanya ingin kode terhadap abstraksi di mana itu non-nol), tetapi null dalam skenario luar biasa / terbatas (yaitu: mengakses yang saat ini dicatat di pengguna, yang tidak pernah nol kecuali saat login awal / pada layar login)
Marchy
@Marki, Anda dapat menggunakan Lazy+ yang disimpan secara eksplisit .isInitialized()untuk melakukan itu. Saya kira tidak ada cara langsung untuk memeriksa properti seperti itu nullkarena jaminan yang tidak dapat Anda peroleh nulldari properti itu. :) Lihat demo ini .
hotkey
@ hotkey Apakah ada gunanya menggunakan terlalu banyak by lazydapat memperlambat waktu pembuatan atau runtime?
Dr.jacky
Saya menyukai gagasan lateinituntuk menghindari penggunaan nulluntuk nilai yang tidak diinisialisasi. Selain itu nulljangan pernah digunakan, dan dengan lateinitnol dapat dihilangkan. Begitulah cara saya mencintai Kotlin :)
KenIchi
26

Selain hotkeyjawaban yang bagus, berikut adalah cara saya memilih di antara keduanya dalam praktik:

lateinit adalah untuk inisialisasi eksternal: ketika Anda membutuhkan hal-hal eksternal untuk menginisialisasi nilai Anda dengan memanggil metode.

misalnya dengan menelepon:

private lateinit var value: MyClass

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

Sedangkan lazysaat itu hanya menggunakan dependensi internal untuk objek Anda.

Guillaume
sumber
1
Saya pikir kita masih bisa malas menginisialisasi bahkan jika itu tergantung pada objek eksternal. Hanya perlu meneruskan nilai ke variabel internal. Dan gunakan variabel internal selama inisialisasi malas. Tapi itu sealami Lateinit.
Elye
Pendekatan ini melempar UninitializedPropertyAccessException, saya memeriksa ulang bahwa saya memanggil fungsi setter sebelum menggunakan nilainya. Apakah ada aturan khusus yang saya lewatkan dengan lateinit? Dalam jawaban Anda ganti MyClass dan Any dengan Konteks android, itulah kasus saya.
Talha
24

Jawaban yang sangat singkat dan ringkas

lateinit: Ini menginisialisasi properti non-null belakangan ini

Tidak seperti inisialisasi malas, lateinit memungkinkan kompiler untuk mengenali bahwa nilai properti non-null tidak disimpan dalam tahap konstruktor untuk dikompilasi secara normal.

Inisialisasi malas

oleh lazy mungkin sangat berguna ketika mengimplementasikan properti read-only (val) yang melakukan inisialisasi lazy di Kotlin.

oleh lazy {...} melakukan inisialisasi di mana properti yang didefinisikan pertama kali digunakan, bukan deklarasi.

John Wick
sumber
jawaban yang bagus, terutama "melakukan inisialisasi di mana properti yang didefinisikan pertama kali digunakan, bukan deklarasi"
user1489829
17

lateinit vs malas

  1. keterlambatan

    i) Gunakan dengan variabel yang bisa berubah [var]

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

    ii) Diizinkan dengan hanya tipe data yang tidak dapat dibatalkan

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

    iii) Ini adalah janji untuk mengkompilasi bahwa nilai akan diinisialisasi di masa depan.

CATATAN : Jika Anda mencoba mengakses variabel lateinit tanpa menginisialisasi, maka ia melempar UnInitializedPropertyAccessException.

  1. malas

    i) Inisialisasi malas dirancang untuk mencegah inisialisasi objek yang tidak perlu.

    ii) Variabel Anda tidak akan diinisialisasi kecuali Anda menggunakannya.

    iii) Ini diinisialisasi hanya sekali. Lain kali saat Anda menggunakannya, Anda mendapatkan nilai dari memori cache.

    iv) Ini adalah thread aman (Ini diinisialisasi dalam utas di mana ia digunakan untuk pertama kalinya. Thread lain menggunakan nilai yang sama yang disimpan dalam cache).

    v) Variabel hanya dapat berupa val .

    vi) Variabel hanya bisa non- nullable .

Geeta Gupta
sumber
7
Saya pikir dalam variabel malas tidak boleh var.
Däñish Shärmà
4

Selain semua jawaban hebat, ada konsep yang disebut pemuatan malas:

Lazy loading adalah pola desain yang biasa digunakan dalam pemrograman komputer untuk menunda inisialisasi suatu objek sampai pada titik dibutuhkannya.

Dengan menggunakannya dengan benar, Anda dapat mengurangi waktu pemuatan aplikasi Anda. Dan cara implementasi Kotlin adalah dengan lazy()mana memuat nilai yang dibutuhkan ke variabel Anda setiap kali dibutuhkan.

Tetapi lateinit digunakan ketika Anda yakin suatu variabel tidak akan nol atau kosong dan akan diinisialisasi sebelum Anda menggunakannya -menggunakan onResume()metode untuk android- dan jadi Anda tidak ingin mendeklarasikannya sebagai tipe nullable.

Mehrbod Khiabani
sumber
Ya, saya juga menginisialisasi onCreateView, onResumedan lainnya dengan lateinit, tetapi kadang-kadang kesalahan terjadi di sana (karena beberapa peristiwa dimulai sebelumnya). Jadi mungkin by lazybisa memberikan hasil yang sesuai. Saya menggunakan lateinituntuk variabel non-nol yang dapat berubah selama siklus hidup.
CoolMind
2

Semuanya benar di atas, tetapi salah satu fakta penjelasan sederhana LAZY ---- Ada beberapa kasus ketika Anda ingin menunda pembuatan instance objek Anda hingga penggunaan pertama. Teknik ini dikenal sebagai inisialisasi malas atau malas instantiation. Tujuan utama inisialisasi malas adalah untuk meningkatkan kinerja dan mengurangi jejak memori Anda. Jika instantiasi instance dari jenis Anda membawa biaya komputasi yang besar dan program mungkin berakhir tidak benar-benar menggunakannya, Anda ingin menunda atau bahkan menghindari pemborosan siklus CPU.

pengguna9830926
sumber
0

Jika Anda menggunakan wadah Musim Semi dan Anda ingin menginisialisasi bidang kacang yang tidak dapat dibatalkan, lateinitlebih cocok.

    @Autowired
    lateinit var myBean: MyBean
mpprdev
sumber
1
harus seperti@Autowired lateinit var myBean: MyBean
Cnfn
0

Jika Anda menggunakan variabel yang tidak berubah, maka lebih baik menginisialisasi dengan by lazy { ... }atau val. Dalam hal ini Anda dapat yakin bahwa itu akan selalu diinisialisasi ketika dibutuhkan dan paling banyak 1 kali.

Jika Anda menginginkan variabel non-null, yang dapat mengubah nilainya, gunakan lateinit var. Dalam pengembangan Android nanti dapat menginisialisasi dalam acara-acara seperti seperti onCreate, onResume. Perlu diketahui, bahwa jika Anda memanggil permintaan REST dan mengakses variabel ini, itu dapat menyebabkan pengecualian UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized, karena permintaan dapat mengeksekusi lebih cepat dari yang dapat diinisialisasi variabel itu.

CoolMind
sumber