Permulaan Malas di Kotlin

1. Gambaran keseluruhan

Dalam artikel ini, kita akan melihat salah satu ciri paling menarik dalam sintaks Kotlin - inisialisasi malas.

Kami juga akan melihat kata kunci lateinit yang membolehkan kami mengelabui penyusun dan memulakan bidang yang tidak kosong di bahagian kelas - bukannya di dalam konstruktor.

2. Corak Permulaan Malas di Jawa

Kadang kala kita perlu membina objek yang mempunyai proses inisialisasi yang membebankan. Juga, seringkali kita tidak dapat memastikan bahawa objek, yang telah kita bayar untuk biaya permulaan pada awal program kita, akan digunakan dalam program kita sama sekali.

Konsep 'inisialisasi malas' dirancang untuk mengelakkan inisialisasi objek yang tidak perlu . Di Jawa, membuat objek dengan cara malas dan selamat dari benang bukanlah perkara yang mudah dilakukan. Corak seperti Singleton mempunyai kelemahan yang signifikan dalam multithreading, pengujian, dan lain-lain - dan kini mereka dikenali sebagai anti-pola yang harus dielakkan.

Sebagai alternatif, kita dapat memanfaatkan inisialisasi statik objek dalaman di Jawa untuk mencapai kemalasan:

public class ClassWithHeavyInitialization { private ClassWithHeavyInitialization() { } private static class LazyHolder { public static final ClassWithHeavyInitialization INSTANCE = new ClassWithHeavyInitialization(); } public static ClassWithHeavyInitialization getInstance() { return LazyHolder.INSTANCE; } }

Perhatikan caranya, hanya apabila kita memanggil kaedah getInstance () pada ClassWithHeavyInitialization , kelas LazyHolder statis akan dimuat, dan contoh baru ClassWithHeavyInitialization akan dibuat. Seterusnya, contoh akan diberikan kepada rujukan INSTANCE akhir statik .

Kami dapat menguji bahawa getInstance () mengembalikan contoh yang sama setiap kali dipanggil:

@Test public void giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall() { // when ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance(); ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance(); // then assertTrue(classWithHeavyInitialization == classWithHeavyInitialization2); }

Itu secara teknikalnya OK tetapi tentunya terlalu rumit untuk konsep yang begitu sederhana .

3. Permulaan Malas di Kotlin

Kita dapat melihat bahawa menggunakan corak inisialisasi malas di Java agak membebankan. Kita perlu menulis banyak boilerplate code untuk mencapai tujuan kita. Nasib baik, bahasa Kotlin mempunyai sokongan terpadu untuk permulaan yang malas .

Untuk membuat objek yang akan diinisialisasi pada akses pertama ke sana, kita dapat menggunakan kaedah malas :

@Test fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce() { // given val numberOfInitializations: AtomicInteger = AtomicInteger() val lazyValue: ClassWithHeavyInitialization by lazy { numberOfInitializations.incrementAndGet() ClassWithHeavyInitialization() } // when println(lazyValue) println(lazyValue) // then assertEquals(numberOfInitializations.get(), 1) }

Seperti yang kita lihat, lambda yang dilewatkan ke fungsi malas itu dilaksanakan hanya sekali.

Semasa kami mengakses lazyValue untuk pertama kalinya - permulaan yang sebenarnya berlaku, dan contoh kelas ClassWithHeavyInitialization yang dikembalikan diberikan kepada rujukan lazyValue . Akses seterusnya ke lazyValue mengembalikan objek yang dimulakan sebelumnya.

Kita boleh menyampaikan LazyThreadSafetyMode sebagai hujah kepada fungsi malas . Mod penerbitan lalai disinkronkan , yang bermaksud bahawa hanya satu utas yang dapat menginisialisasi objek yang diberikan.

Kita boleh lulus PUBLICATION sebagai mod - yang menyebabkan setiap utas dapat memulakan harta yang diberikan. Objek yang ditugaskan untuk rujukan akan menjadi nilai pertama yang dikembalikan - jadi utas pertama menang.

Mari lihat senario itu:

@Test fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce() { // given val numberOfInitializations: AtomicInteger = AtomicInteger() val lazyValue: ClassWithHeavyInitialization by lazy(LazyThreadSafetyMode.PUBLICATION) { numberOfInitializations.incrementAndGet() ClassWithHeavyInitialization() } val executorService = Executors.newFixedThreadPool(2) val countDownLatch = CountDownLatch(1) // when executorService.submit { countDownLatch.await(); println(lazyValue) } executorService.submit { countDownLatch.await(); println(lazyValue) } countDownLatch.countDown() // then executorService.awaitTermination(1, TimeUnit.SECONDS) executorService.shutdown() assertEquals(numberOfInitializations.get(), 2) }

Kita dapat melihat bahawa memulakan dua utas pada masa yang sama menyebabkan inisialisasi ClassWithHeavyInitialization berlaku dua kali.

Terdapat juga mod ketiga - TIADA - tetapi tidak boleh digunakan di persekitaran multithread kerana tingkah lakunya tidak ditentukan.

4. lateinit Kotlin

Di Kotlin, setiap harta kelas yang tidak boleh ditolak yang diisytiharkan di dalam kelas harus diinisialisasi sama ada dalam konstruktor atau sebagai sebahagian daripada deklarasi pemboleh ubah. Sekiranya kita gagal melakukannya, penyusun Kotlin akan mengeluh dengan mesej ralat:

Kotlin: Property must be initialized or be abstract

Ini pada dasarnya bermaksud bahawa kita harus memulakan pemboleh ubah atau menandakannya sebagai abstrak .

Sebaliknya, terdapat beberapa kes di mana pemboleh ubah dapat diberikan secara dinamik dengan contoh suntikan kebergantungan.

Untuk menangguhkan inisialisasi pemboleh ubah, kita dapat menentukan bahawa medan adalah lateinit . Kami memberitahu penyusun bahawa pemboleh ubah ini akan diberikan kemudian dan kami membebaskan penyusun dari tanggungjawab untuk memastikan pemboleh ubah ini diinisialisasi:

lateinit var a: String @Test fun givenLateInitProperty_whenAccessItAfterInit_thenPass() { // when a = "it" println(a) // then not throw }

Sekiranya kita lupa untuk menginisialisasi harta benda lateinit , kita akan mendapat UninitializedPropertyAccessException :

@Test(expected = UninitializedPropertyAccessException::class) fun givenLateInitProperty_whenAccessItWithoutInit_thenThrow() { // when println(a) }

Perlu disebutkan bahawa kita hanya boleh menggunakan pemboleh ubah lateinit dengan jenis data bukan primitif. Oleh itu, tidak mungkin menulis sesuatu seperti ini:

lateinit var value: Int

Dan sekiranya kami melakukannya, kami akan mendapat ralat kompilasi:

Kotlin: 'lateinit' modifier is not allowed on properties of primitive types

5. Kesimpulan

Dalam tutorial ringkas ini, kami melihat permulaan objek yang malas.

Pertama, kami melihat bagaimana membuat inisial malas yang selamat di utas di Java. Kami melihat bahawa ia sangat membebankan dan memerlukan banyak kod plat boiler.

Seterusnya, kami mencari kata kunci malas Kotlin yang digunakan untuk inisialisasi sifat malas. Pada akhirnya, kami melihat bagaimana menangguhkan menetapkan pemboleh ubah menggunakan kata kunci lateinit .

Pelaksanaan semua contoh dan coretan kod ini boleh didapati di GitHub.