Lenivá inicializácia v Kotline

1. Prehľad

V tomto článku sa pozrieme na jednu z najzaujímavejších funkcií syntaxe Kotlin - lenivú inicializáciu.

Pozrime sa tiež na lateinit kľúčové slovo, ktoré nám umožňuje oklamať kompilátor a inicializovať nenulové polia v tele triedy - namiesto v konštruktore.

2. Líný inicializačný vzor v Jave

Niekedy musíme postaviť objekty, ktoré majú ťažkopádny proces inicializácie. Často si tiež nemôžeme byť istí, že objekt, za ktorý sme na začiatku nášho programu zaplatili náklady na inicializáciu, bude v našom programe vôbec použitý.

Koncepcia Funkcia „lenivá inicializácia“ bola navrhnutá tak, aby zabránila zbytočnej inicializácii objektov. V Jave nie je ľahké vytvoriť objekt lenivým spôsobom a bezpečným pre vlákna. Vzory ako Singleton majú významné nedostatky v multithreadingu, testovaní atď. - a dnes sú všeobecne známe ako anti-vzory, ktorým sa treba vyhnúť.

Prípadne môžeme na dosiahnutie lenivosti využiť statickú inicializáciu vnútorného objektu v Jave:

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

Všimnite si, ako, len keď zavoláme getInstance () metóda na ClassWithHeavyInitialization, statická LazyHolder načíta sa nová trieda a ClassWithHeavyInitialization sa vytvorí. Ďalej bude inštancia priradená k statickýkonečnéINSTANCE odkaz.

Môžeme to otestovať getInstance () vracia rovnakú inštanciu zakaždým, keď sa volá:

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

To je technicky v poriadku, ale samozrejme trochu komplikované pre taký jednoduchý koncept.

3. Lenivá inicializácia v Kotline

Vidíme, že použitie lenivého inicializačného vzoru v Jave je dosť ťažkopádne. Aby sme dosiahli náš cieľ, musíme napísať veľa štandardných kódov. Našťastie má jazyk Kotlin zabudovanú podporu pre lenivú inicializáciu.

Na vytvorenie objektu, ktorý sa inicializuje pri prvom prístupe k nemu, môžeme použiť lenivý metóda:

@Testová zábava danéLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce () {// dané číslo valentuOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization by lenivý {numberOfInitializations.incrementAndialValue numberOfInitializations.get (), 1)}

Ako vidíme, lambda prešla na lenivý funkcia bola vykonaná iba raz.

Keď vstupujeme do lazyValue po prvýkrát - došlo k skutočnej inicializácii a vrátená inštancia súboru ClassWithHeavyInitialization trieda bola pridelená do lazyValue odkaz. Následný prístup k lazyValue vrátil predtým inicializovaný objekt.

Môžeme prejsť LazyThreadSafetyMode ako argument pre lenivý funkcie. Predvolený režim publikovania je SYNCHRONIZOVANÝ, čo znamená, že daný objekt môže inicializovať iba jedno vlákno.

Môžeme prejsť a PUBLIKÁCIA ako režim - čo spôsobí, že každé vlákno môže inicializovať danú vlastnosť. Objektom priradeným k referencii bude prvá vrátená hodnota - vyhrá teda prvé vlákno.

Pozrime sa na tento scenár:

@Test fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce () {// uvedenej Val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization oneskoreného (LazyThreadSafetyMode.PUBLICATION) {numberOfInitializations.incrementAndGet () ClassWithHeavyInitialization ()} val executorService = Executors.newFixedThreadPool (2) val countDownLatch = CountDownLatch (1) // keď executorService.submit {countDownLatch.await (); println (lazyValue)} executorService.submit {countDownLatch.await (); println (lazyValue)} countDownLatch.countDown () // potom executorService.awaitTermination (1, TimeUnit.SECONDS) executorService.shutdown () assertEquals (numberOfInitializations.get (), 2)}

Vidíme, že začatie dvoch vlákien súčasne spôsobuje inicializáciu ClassWithHeavyInitialization stať sa dvakrát.

K dispozícii je tiež tretí režim - ŽIADNE - ale nemal by sa používať v prostredí s viacerými vláknami, pretože jeho správanie nie je definované.

4. Kotlinova lateinit

V Kotline by sa mala každá nenulová vlastnosť triedy, ktorá je deklarovaná v triede, inicializovať buď v konštruktore alebo ako súčasť deklarácie premennej. Ak to neurobíme, potom sa kompilátor Kotlin sťažuje na chybové hlásenie:

Kotlin: Vlastnosť musí byť inicializovaná alebo musí byť abstraktná

To v podstate znamená, že by sme mali premennú buď inicializovať, alebo označiť ako abstraktné.

Na druhej strane existujú niektoré prípady, v ktorých je možné premennú dynamicky priradiť napríklad vložením závislosti.

Aby sme odložili inicializáciu premennej, môžeme určiť, že sa jedná o pole lateinit. Informujeme kompilátor, že táto premenná bude priradená neskôr, a zbavujeme kompilátor zodpovednosti za zabezpečenie inicializácie tejto premennej:

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

Ak zabudneme inicializovať lateinit majetok, dostaneme UninitializedPropertyAccessException:

@Test (očakáva sa = UninitializedPropertyAccessException :: class) zábava givenLateInitProperty_whenAccessItWithoutInit_thenThrow () {// pri tlači (a)}

Za zmienku stojí, že môžeme používať iba lateinit premenné s neprimitívnymi dátovými typmi. Preto nie je možné napísať niečo také:

hodnota lateinit var: Int

A ak tak urobíme, dostali by sme chybu kompilácie:

Kotlin: Modifikátor 'lateinit' nie je povolený pre vlastnosti primitívnych typov

5. Záver

V tomto rýchlom návode sme sa pozreli na lenivú inicializáciu objektov.

Najskôr sme videli, ako vytvoriť v Java bezpečnú lenivú inicializáciu. Videli sme, že je to veľmi ťažkopádne a vyžaduje veľa štandardného kódu.

Ďalej sme sa ponorili do Kotlina lenivý kľúčové slovo, ktoré sa používa na lenivú inicializáciu vlastností. Na konci sme videli, ako odložiť priradenie premenných pomocou lateinit kľúčové slovo.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found