Luie initialisatie in Kotlin

1. Overzicht

In dit artikel zullen we een van de meest interessante functies in de Kotlin-syntaxis bekijken: luie initialisatie.

We zullen ook kijken naar de laat in het begin trefwoord waarmee we de compiler kunnen misleiden en niet-null-velden in de body van de klasse kunnen initialiseren - in plaats van in de constructor.

2. Lazy Initialization Pattern in Java

Soms moeten we objecten construeren die een omslachtig initialisatieproces hebben. Ook kunnen we er vaak niet zeker van zijn dat het object, waarvoor we bij aanvang van ons programma de initialisatiekosten hebben betaald, überhaupt in ons programma zal worden gebruikt.

Het concept van ‘Luie initialisatie 'is ontworpen om onnodige initialisatie van objecten te voorkomen. In Java is het niet eenvoudig om op een luie en threadveilige manier een object te maken. Patronen zoals Singleton hebben aanzienlijke tekortkomingen in multithreading, testen, enz. - en ze staan ​​nu algemeen bekend als antipatronen die moeten worden vermeden.

Als alternatief kunnen we de statische initialisatie van het innerlijke object in Java gebruiken om luiheid te bereiken:

openbare klasse ClassWithHeavyInitialization {privé ClassWithHeavyInitialization () {} privé statische klasse LazyHolder {openbare statische laatste ClassWithHeavyInitialization INSTANCE = nieuwe ClassWithHeavyInitialization (); } openbare statische ClassWithHeavyInitialization getInstance () {terugkeer LazyHolder.INSTANCE; }}

Merk op hoe, alleen wanneer we de getInstance () methode op ClassWithHeavyInitialization, de statische LazyHolder klasse wordt geladen en de nieuwe instantie van de ClassWithHeavyInitialization zal gemaakt worden. Vervolgens wordt de instantie toegewezen aan de statischlaatsteVOORBEELD referentie.

We kunnen testen dat het getInstance () retourneert dezelfde instantie elke keer dat het wordt aangeroepen:

@Test openbare ongeldige giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall () {// wanneer ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance (); ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance (); // dan assertTrue (classWithHeavyInitialization == classWithHeavyInitialization2); }

Dat is technisch oké, maar natuurlijk een beetje te ingewikkeld voor zo'n eenvoudig concept.

3. Luie initialisatie in Kotlin

We kunnen zien dat het gebruik van het luie initialisatiepatroon in Java behoorlijk omslachtig is. We moeten veel standaardcode schrijven om ons doel te bereiken. Gelukkig heeft de Kotlin-taal ingebouwde ondersteuning voor luie initialisatie.

Om een ​​object te maken dat wordt geïnitialiseerd bij de eerste toegang ertoe, kunnen we de lui methode:

@Test plezier gegevenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce () {// gegeven waarde numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization door lazy {numberOfInitialization printGlassization (dan lazy) numberOfInitializations.get (), 1)}

Zoals we kunnen zien, ging de lambda over naar de lui functie is slechts één keer uitgevoerd.

Wanneer we het lazyValue voor de eerste keer - er vond een daadwerkelijke initialisatie plaats en het geretourneerde exemplaar van het ClassWithHeavyInitialization klasse is toegewezen aan de lazyValue referentie. Daaropvolgende toegang tot het lazyValue heeft het eerder geïnitialiseerde object geretourneerd.

We kunnen de LazyThreadSafetyMode als argument voor de lui functie. De standaard publicatiemodus is GESYNCHRONISEERD, wat betekent dat slechts een enkele thread het gegeven object kan initialiseren.

We kunnen een PUBLICATIE als een modus - wat ervoor zorgt dat elke thread een gegeven eigenschap kan initialiseren. Het object dat aan de referentie is toegewezen, is de eerste geretourneerde waarde - dus de eerste thread wint.

Laten we dat scenario eens bekijken:

@Test fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce () {// gegeven val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization door lui (LazyThreadSafetyMode.PUBLICATION) {numberOfInitializations.incrementAndGet () ClassWithHeavyInitialization ()} val ExecutorService = Executors.newFixedThreadPool (2) val countDownLatch = CountDownLatch (1) // wanneer executorService.submit {countDownLatch.await (); println (lazyValue)} executorService.submit {countDownLatch.await (); println (lazyValue)} countDownLatch.countDown () // dan executorService.awaitTermination (1, TimeUnit.SECONDS) executorService.shutdown () assertEquals (numberOfInitializations.get (), 2)}

We kunnen zien dat het gelijktijdig starten van twee threads de initialisatie van het ClassWithHeavyInitialization twee keer gebeuren.

Er is ook een derde modus - GEEN - maar het mag niet worden gebruikt in een omgeving met meerdere threads, aangezien het gedrag ervan ongedefinieerd is.

4. Kotlin's laat in het begin

In Kotlin moet elke niet-nulbare klasse-eigenschap die in de klasse wordt gedeclareerd, worden geïnitialiseerd in de constructor of als onderdeel van de variabele declaratie. Als we dat niet doen, zal de Kotlin-compiler klagen met een foutmelding:

Kotlin: eigenschap moet worden geïnitialiseerd of abstract zijn

Dit betekent in feite dat we de variabele moeten initialiseren of markeren als abstract.

Aan de andere kant zijn er enkele gevallen waarin de variabele dynamisch kan worden toegewezen door bijvoorbeeld afhankelijkheidsinjectie.

Om de initialisatie van de variabele uit te stellen, kunnen we specificeren dat een veld dat is laat in het begin. We informeren de compiler dat deze variabele later zal worden toegewezen en we bevrijden de compiler van de verantwoordelijkheid om ervoor te zorgen dat deze variabele wordt geïnitialiseerd:

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

Als we vergeten het laat in het begin eigendom, we krijgen een UninitializedPropertyAccessException:

@Test (verwacht = UninitializedPropertyAccessException :: class) plezier gegevenLateInitProperty_whenAccessItWithoutInit_thenThrow () {// when println (a)}

Het is vermeldenswaard dat we alleen kunnen gebruiken laat in het begin variabelen met niet-primitieve gegevenstypen. Daarom is het niet mogelijk om zoiets als dit te schrijven:

lateinit var waarde: Int

En als we dat doen, krijgen we een compilatiefout:

Kotlin: 'lateinit' modifier is niet toegestaan ​​op eigenschappen van primitieve types

5. Conclusie

In deze korte tutorial hebben we gekeken naar de luie initialisatie van objecten.

Ten eerste hebben we gezien hoe we een threadveilige luie initialisatie in Java kunnen maken. We zagen dat het erg omslachtig is en veel standaardcode nodig heeft.

Vervolgens doken we in Kotlin lui trefwoord dat wordt gebruikt voor luie initialisatie van eigenschappen. Uiteindelijk hebben we gezien hoe we het toewijzen van variabelen kunnen uitstellen met behulp van de laat in het begin trefwoord.

De implementatie van al deze voorbeelden en codefragmenten is te vinden op GitHub.