Lijena inicijalizacija u Kotlinu

1. Pregled

U ovom ćemo članku razmotriti jednu od najzanimljivijih značajki u sintaksi Kotlina - lijena inicijalizacija.

Također ćemo pogledati lateinit ključna riječ koja nam omogućuje prevaru kompajlera i inicijalizaciju ne-null polja u tijelu klase - umjesto u konstruktoru.

2. Uzorak lijene inicijalizacije u Javi

Ponekad moramo konstruirati objekte koji imaju glomazan postupak inicijalizacije. Također, često ne možemo biti sigurni da će se objekt, za koji smo platili trošak inicijalizacije na početku našeg programa, uopće koristiti u našem programu.

Koncept 'Lijena inicijalizacija' osmišljena je kako bi se spriječila nepotrebna inicijalizacija objekata. U Javi nije lako napraviti objekt na lijen način i siguran način. Obrasci poput Singleton imaju značajne nedostatke u multithreadingu, testiranju itd. - i sada su nadaleko poznati kao anti-uzorci koje treba izbjegavati.

Alternativno, možemo iskoristiti statičku inicijalizaciju unutarnjeg objekta u Javi kako bismo postigli lijenost:

javna klasa ClassWithHeavyInitialization {private ClassWithHeavyInitialization () {} privatna statička klasa LazyHolder {javna statička konačna ClassWithHeavyInitialization INSTANCE = nova ClassWithHeavyInitialization (); } javna statička ClassWithHeavyInitialization getInstance () {return LazyHolder.INSTANCE; }}

Primijetite kako, samo kada ćemo nazvati getInstance () metoda na ClassWithHeavyInitialization, statički LazyHolder klasa će se učitati i nova instanca ClassWithHeavyInitialization stvorit će se. Dalje, instanca će biti dodijeljena statičkikonačniPRIMJER referenca.

Možemo testirati da getInstance () vraća istu instancu svaki put kad je pozvana:

@Test javna praznina giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall () {// when ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance (); ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance (); // zatim assertTrue (classWithHeavyInitialization == classWithHeavyInitialization2); }

To je tehnički u redu, ali naravno malo previše komplicirano za tako jednostavan koncept.

3. Lijena inicijalizacija u Kotlinu

Vidimo da je korištenje lijenog uzorka inicijalizacije u Javi prilično nezgrapno. Moramo napisati puno šifri kako bismo postigli svoj cilj. Srećom, jezik Kotlin ima ugrađenu podršku za lijenu inicijalizaciju.

Da bismo stvorili objekt koji će se inicijalizirati pri prvom pristupu njemu, možemo koristiti lijen metoda:

@Test zabava givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce () {// podana val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization by lezy {numberOfInitializations.increment (In) Class (print) numberOfInitializations.get (), 1)}

Kao što vidimo, lambda je prešla na lijen funkcija izvršena samo jednom.

Kad pristupamo lijenaVrijednost po prvi puta - dogodila se stvarna inicijalizacija i vraćena instanca ClassWithHeavyInitialization razred dodijeljen je lijenaVrijednost referenca. Naknadni pristup lijenaVrijednost vratio prethodno inicijalizirani objekt.

Možemo proći LazyThreadSafetyMode kao argument za lijen funkcija. Zadani način objavljivanja je SINKRONIZIRANO, što znači da samo jedna nit može inicijalizirati zadani objekt.

Možemo proći a OBJAVLJIVANJE kao način - što će uzrokovati da svaka nit može inicijalizirati dano svojstvo. Objekt dodijeljen referenci bit će prva vraćena vrijednost - tako da prva nit pobjeđuje.

Pogledajmo taj scenarij:

@Test zabava whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce () {// navedeni Val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization od lijen (LazyThreadSafetyMode.PUBLICATION) {numberOfInitializations.incrementAndGet () ClassWithHeavyInitialization ()} val = executorService Executors.newFixedThreadPool (2) val countDownLatch = CountDownLatch (1) // kada je executorService.submit {countDownLatch.await (); println (lazyValue)} executorService.submit {countDownLatch.await (); println (lazyValue)} countDownLatch.countDown () // zatim executorService.awaitTermination (1, TimeUnit.SECONDS) executorService.shutdown () assertEquals (numberOfInitializations.get (), 2)}

Možemo vidjeti da pokretanje dvije niti istovremeno uzrokuje inicijalizaciju ClassWithHeavyInitialization da se dogodi dva puta.

Postoji i treći način rada - NITKO - ali ga se ne smije koristiti u višenitnom okruženju jer njegovo ponašanje nije definirano.

4. Kotlinov lateinit

U Kotlinu, svako svojstvo klase koje se ne može poništiti koje je deklarirano u klasi treba biti inicijalizirano ili u konstruktoru ili kao dio deklaracije varijable. Ako to ne uspijemo, kompajler Kotlina žalit će se s porukom o pogrešci:

Kotlin: Svojstvo mora biti inicijalizirano ili apstraktno

To u osnovi znači da bismo trebali ili inicijalizirati varijablu ili je označiti kao sažetak.

S druge strane, postoje neki slučajevi u kojima se varijabla može dinamički dodijeliti, na primjer ubrizgavanjem ovisnosti.

Da bismo odgodili inicijalizaciju varijable, možemo odrediti da je polje lateinit. Obavještavamo prevoditelj da će ova varijabla will biti dodijeljena kasnije i oslobađamo prevoditelj odgovornosti da osigura da se ova varijabla inicijalizira:

lateinit var a: String @Test zabava givenLateInitProperty_whenAccessItAfterInit_thenPass () {// kada je a = "it" println (a) // tada se ne baca}

Ako zaboravimo inicijalizirati lateinit imovine, dobit ćemo UninitializedPropertyAccessException:

@Test (očekuje se = UninitializedPropertyAccessException :: class) zabava givenLateInitProperty_whenAccessItWithoutInit_thenThrow () {// kada println (a)}

Vrijedno je spomenuti da možemo samo koristiti lateinit varijable s neprimitivnim vrstama podataka. Stoga nije moguće napisati ovako nešto:

vrijednost lateinit var: Int

A ako to učinimo, dobit ćemo pogrešku u kompilaciji:

Kotlin: modifikator 'lateinit' nije dopušten na svojstvima primitivnih tipova

5. Zaključak

U ovom smo brzom vodiču pogledali lijenu inicijalizaciju objekata.

Prvo, vidjeli smo kako stvoriti lijenu inicijalizaciju sigurnu u nit u Javi. Vidjeli smo da je to vrlo glomazno i ​​da mu treba puno šifre.

Dalje smo se upuštali u Kotlin lijen ključna riječ koja se koristi za lijenu inicijalizaciju svojstava. Na kraju smo vidjeli kako odgoditi dodjeljivanje varijabli pomoću lateinit ključna riječ.

Provedbu svih ovih primjera i isječaka koda možete pronaći na GitHubu.