Lat initialisering i Kotlin

1. Oversikt

I denne artikkelen ser vi på en av de mest interessante funksjonene i Kotlin-syntaksen - lat initialisering.

Vi vil også se på lateinit nøkkelord som lar oss lure kompilatoren og initialisere ikke-null-felt i klassen - i stedet for i konstruktøren.

2. Lat initialiseringsmønster i Java

Noen ganger trenger vi å konstruere objekter som har en tungvint initialiseringsprosess. Ofte kan vi ikke være sikre på at objektet, som vi betalte initialiseringskostnadene for ved starten av programmet vårt, i det hele tatt vil bli brukt i vårt program.

Konseptet av ‘Lat initialisering’ ble designet for å forhindre unødvendig initialisering av objekter. I Java er det ikke lett å lage et objekt på en lat og trådsikker måte. Mønstre som Singleton har betydelige feil i flertråding, testing, etc. - og de er nå allment kjent som antimønstre som skal unngås.

Alternativt kan vi utnytte den statiske initialiseringen av det indre objektet i Java for å oppnå latskap:

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

Legg merke til hvordan, bare når vi vil ringe getInstance () metode på ClassWithHeavyInitialization, det statiske LazyHolder klasse lastes inn, og den nye forekomsten av ClassWithHeavyInitialization vil bli opprettet. Deretter tildeles forekomsten til statiskendeligFOREKOMST henvisning.

Vi kan teste at getInstance () returnerer samme forekomst hver gang det heter:

@Test offentlig ugyldighet giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall () {// når ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance (); ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance (); // deretter assertTrue (classWithHeavyInitialization == classWithHeavyInitialization2); }

Det er teknisk OK, men selvfølgelig litt for komplisert for et så enkelt konsept.

3. Lat initialisering i Kotlin

Vi kan se at bruk av lat initialiseringsmønster i Java er ganske tungvint. Vi trenger å skrive mye kokkel for å oppnå målet vårt. Heldigvis har Kotlin-språket innebygd støtte for lat initialisering.

For å lage et objekt som vil bli initialisert ved første tilgang til det, kan vi bruke lat metode:

@Test fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce () {// given val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization by lazy {numberOfInitializations.incrementAnd numberOfInitializations.get (), 1)}

Som vi kan se, gikk lambda over til lat funksjonen ble bare utført en gang.

Når vi får tilgang til lazyValue for første gang - en faktisk initialisering skjedde, og den returnerte forekomsten av ClassWithHeavyInitialization klassen ble tildelt lazyValue henvisning. Senere tilgang til lazyValue returnerte det tidligere initialiserte objektet.

Vi kan passere LazyThreadSafetyMode som et argument til lat funksjon. Standard publikasjonsmodus er SYNKRONISERT, noe som betyr at bare en enkelt tråd kan initialisere det gitte objektet.

Vi kan passere en UTGIVELSE som en modus - som vil føre til at hver tråd kan initialisere gitt eiendom. Objektet som tildeles referansen vil være den første returnerte verdien - så den første tråden vinner.

La oss se på dette scenariet:

@Test moro whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce () {// gitt val numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization ved lat (LazyThreadSafetyMode.PUBLICATION) {numberOfInitializations.incrementAndGet () ClassWithHeavyInitialization ()} val executorService = Executors.newFixedThreadPool (2) val countDownLatch = CountDownLatch (1) // når executorService.submit {countDownLatch.await (); println (lazyValue)} executorService.submit {countDownLatch.await (); println (lazyValue)} countDownLatch.countDown () // deretter executorService.awaitTermination (1, TimeUnit.SECONDS) executorService.shutdown () assertEquals (numberOfInitializations.get (), 2)}

Vi kan se at å starte to tråder samtidig forårsaker initialiseringen av ClassWithHeavyInitialization å skje to ganger.

Det er også en tredje modus - INGEN - men det skal ikke brukes i det flertrådede miljøet, ettersom dets oppførsel er udefinert.

4. Kotlin's lateinit

I Kotlin skal alle ikke-nullbare klasseegenskaper som blir erklært i klassen, initialiseres enten i konstruktøren eller som en del av variabelerklæringen. Hvis vi ikke klarer det, vil Kotlin-kompilatoren klage med en feilmelding:

Kotlin: Eiendom må initialiseres eller være abstrakt

Dette betyr i utgangspunktet at vi enten skal initialisere variabelen eller merke den som abstrakt.

På den annen side er det noen tilfeller der variabelen kan tildeles dynamisk ved for eksempel avhengighetsinjeksjon.

For å utsette initialiseringen av variabelen, kan vi spesifisere at et felt er lateinit. Vi informerer kompilatoren om at denne variabelen vil bli tildelt senere, og vi frigjør kompilatoren fra ansvaret for å sørge for at denne variabelen blir initialisert:

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

Hvis vi glemmer å initialisere lateinit eiendom, får vi en UninitializedPropertyAccessException:

@Test (forventet = UninitializedPropertyAccessException :: klasse) moro gittLateInitProperty_whenAccessItWithoutInit_thenThrow () {// når println (a)}

Det er verdt å nevne at vi bare kan bruke lateinit variabler med ikke-primitive datatyper. Derfor er det ikke mulig å skrive noe slikt:

lateinit var verdi: Int

Og hvis vi gjør det, vil vi få en kompileringsfeil:

Kotlin: 'lateinit' modifikator er ikke tillatt på egenskaper av primitive typer

5. Konklusjon

I denne raske opplæringen så vi på lat initialisering av objekter.

For det første så vi hvordan du oppretter en trådsikker lat initialisering i Java. Vi så at det er veldig tungvint og trenger mye kjelekode.

Deretter gikk vi inn i Kotlin lat nøkkelord som brukes til lat initialisering av eiendommer. Til slutt så vi hvordan vi kan utsette tildeling av variabler ved hjelp av lateinit nøkkelord.

Implementeringen av alle disse eksemplene og kodebiter finner du på GitHub.


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