Refleksjon med Kotlin

1. Introduksjon

Refleksjon er navnet på muligheten til å inspisere, laste og samhandle med klasser, felt og metoder under kjøretid. Vi kan gjøre dette selv når vi ikke vet hva de er på kompileringstidspunktet.

Dette har et stort antall bruksområder, avhengig av hva vi utvikler. For eksempel bruker rammeverk som vår den tungt.

Støtte for dette er innebygd i JVM og er dermed implisitt tilgjengelig for alle JVM-baserte språk. Noen JVM-språk har imidlertid ekstra støtte utover det som allerede er tilgjengelig.

2. Java-refleksjon

Alle standard Java Reflection-konstruksjonene er tilgjengelige og fungerer perfekt med vår Kotlin-kode. Dette inkluderer java.lang.Klasse klasse samt alt i java.lang.refleksjon pakke.

Hvis vi av en eller annen grunn vil bruke standard Java Reflection API-er, kan vi gjøre det på nøyaktig samme måte som i Java. For eksempel, for å få en liste over alle de offentlige metodene i en Kotlin-klasse, vil vi gjøre:

MyClass :: class.java.methods

Dette bryter ned i følgende konstruksjoner:

  • MyClass :: klasse gir oss Kotlin Class representasjon for Klassen min klasse
  • .java gir oss java.lang.Klasse tilsvarende
  • .metoder er et kall til java.lang.Class.getMethods () tilgangsmetode

Dette vil fungere nøyaktig det samme, enten det kalles fra Java eller Kotlin, og om det kalles på en Java- eller en Kotlin-klasse. Dette inkluderer Kotlin-spesifikke konstruksjoner, for eksempel dataklasser.

dataklasse ExampleDataClass (val navn: String, var aktivert: Boolean) ExampleDataClass :: class.java.methods.forEach (:: println)

Kotlin konverterer også de returnerte typene til Kotlin-representasjonene.

I ovenstående får vi en kotlin. matrise som vi kan ringe på for hver().

3. Forbedringer av Kotlin-refleksjon

Selv om vi kan bruke standard Java Reflection API-er, er det ikke kjent med alle utvidelser som Kotlin bringer til plattformen..

I tillegg kan det noen ganger være litt vanskelig å bruke i noen situasjoner. Kotlin bringer sin egen refleksjon API som vi kan bruke som løser disse problemene.

Alle inngangspunkter i Kotlin Reflection API bruker referanser. Tidligere så vi bruken av ::klasse for å gi en referanse til klassedefinisjonen. Vi vil også kunne bruke dette for å få referanser til metoder og egenskaper.

3.1. Kotlin-klassereferanser

Kotlin Reflection API gir tilgang til en klassereferanse. Dette kan deretter brukes til å introdusere de fulle detaljene i Kotlin-klassen. Dette gir tilgang til Java Class referanse - java.lang.Klasse objekt - men også til alle de Kotlin-spesifikke detaljene.

Kotlin API for klassedetaljer sentrerer rundt kotlin.reflect.KClass klasse. Dette er tilgjengelig ved å bruke :: operatør fra hvilket som helst klassenavn eller forekomst - f.eks. String :: klasse.

Alternativt kan du få tilgang til den ved hjelp av utvidelsesmetoden java.lang.Class.kotlin hvis en Java Klasse forekomst er tilgjengelig for oss:

val listClass: KClass = List :: class val name = "Baeldung" val stringClass: KClass = name :: class val someClass: Class val kotlinClass: KClass = someClass.kotlin

Når vi har fått en KClass objekt, det er noen enkle ting det kan fortelle oss om den aktuelle klassen. Noen av disse er standard Java-konsepter, og andre er Kotlin-spesifikke konsepter.

For eksempel kan vi enkelt finne ut om en klasse er abstrakt eller endelig, men vi kan også finne ut om klassen er en dataklasse eller en ledsagerklasse:

val stringClass = String :: class assertEquals ("kotlin.String", stringClass.qualifiedName) assertFalse (stringClass.isData) assertFalse (stringClass.isCompanion) assertFalse (stringClass.isAbstract) assertTrue (stringClass.isFinal) assertFalse ()

Vi har også måter å bevege oss rundt i klassens hierarki. I Java kan vi allerede flytte fra en klasse til superklassen, grensesnittene og den ytre klassen den er omsluttet av - hvis det er aktuelt.

Kotlin legger til dette muligheten til å få Companion Object for en vilkårlig klasse, og Gjenstand forekomst for en objektklasse:

println (TestWithCompanion :: class.companionObject) println (TestWithCompanion :: class.companionObjectInstance) println (TestObject :: class.objectInstance)

Vi kan også opprette nye forekomster av en klasse fra en klassereferanse, på omtrent samme måte som i Java:

val listClass = ArrayList :: class val list = listClass.createInstance () assertTrue (listen er ArrayList)

Alternativt kan vi få tilgang til konstruktørene og bruke en eksplisitt hvis vi trenger det. Dette er alle metodereferanser som diskutert i neste avsnitt.

På en veldig lignende måte kan vi få tilgang til alle metodene, egenskapene, utvidelsene og andre medlemmer av klassen:

val bigDecimalClass = BigDecimal :: class println (bigDecimalClass.constructors) println (bigDecimalClass.functions) println (bigDecimalClass.memberProperties) println (bigDecimalClass.memberExtensionFunctions)

3.2. Kotlin Method Referanser

I tillegg til å kunne samhandle med klasser, vi kan også samhandle med metoder og egenskaper.

Dette inkluderer klasseegenskaper - definert med val eller var, standard klassemetoder og funksjoner på toppnivå. Som tidligere fungerer dette like bra på kode skrevet i standard Java som den gjør på kode skrevet i Kotlin.

På nøyaktig samme måte som med klasser, vi kan få en referanse til en metode eller eiendom ved hjelp av:: operatør.

Dette ser nøyaktig ut som i Java 8 for å få en metodereferanse, og vi kan bruke den på nøyaktig samme måte. I Kotlin kan denne metoden referanse også brukes til å få refleksjonsinformasjon om målet.

Når vi har fått en metodereferanse, kan vi kalle det som om det virkelig var den aktuelle metoden. Dette er kjent som en kallbar referanse:

val str = "Hello" val lengthMethod = str :: length assertEquals (5, lengthMethod ())

Vi kan også få mer detaljer om selve metoden, på samme måte som vi kan for klasser. Dette inkluderer både standard Java-detaljer og Kotlin-spesifikke detaljer, for eksempel om metoden er en operatør eller hvis det er på linje:

val byteInputStream = String :: byteInputStream assertEquals ("byteInputStream", byteInputStream.name) assertFalse (byteInputStream.isSuspend) assertFalse (byteInputStream.isExternal) assertTrue (byteInputStream.isInline) assertFalse (byteInputStream.isInline) assertFalse (byteInputStream.is)

I tillegg til dette kan vi få mer informasjon om metodens innganger og utganger gjennom denne referansen.

Dette inkluderer detaljer om returtype og parametere, inkludert Kotlin-spesifikke detaljer - for eksempel nullitet og valgfrihet.

val str = "Hello" val method = str :: byteInputStream assertEquals (ByteArrayInputStream :: class.starProjectedType, method.returnType) assertFalse (method.returnType.isMarkedNullable) assertEquals (1, method.parameters.size) assertTrue (method.parameters 0] .isOptional) assertFalse (method.parameters [0] .isVararg) assertEquals (Charset :: class.starProjectedType, method.parameters [0] .type)

3.3. Kotlin Eiendomsreferanser

Dette fungerer nøyaktig det samme for Properties også, selv om det åpenbart er detaljene som kan oppnås er forskjellige. Eiendommer i stedet kan informere oss om de er konstanter, sent initialisert eller foranderlige:

lateinit var mutableProperty: String val mProperty = this :: mutableProperty assertEquals ("mutableProperty", mProperty.name) assertTrue (mProperty.isLateinit) assertFalse (mProperty.isConst) assertTrue (mProperty is KMut

Merk at konseptet med egenskaper også fungerer i en hvilken som helst ikke-Kotlin-kode. Disse identifiseres av felt som følger JavaBeans-konvensjonene angående getter- og settermetoder.

Dette inkluderer klasser i Java-standardbiblioteket. For eksempel Kastbar klasse har en eiendom Kastbar. Melding i kraft av at det finnes en metode getMessage () definert i den.

Vi kan få tilgang til selve eiendommen gjennom metodereferanser som er eksponert - den getter og setter metoder. De setter er bare tilgjengelig hvis vi jobber med en KMutableProperty - dvs. eiendommen ble erklært som var, mens getter er alltid tilgjengelig.

Disse blir utsatt på en enklere måte å bruke via få() og sett() metoder. De getter og setter verdier er faktiske metodereferanser, slik at vi kan jobbe med dem nøyaktig det samme som alle andre metodereferanser:

val prop = this :: mutableProperty assertEquals (String :: class.starProjectedType, prop.getter.returnType) prop.set ("Hello") assertEquals ("Hello", prop.get ()) prop.setter ("World") assertEquals ("World", prop.getter ())

4. Oppsummering

Denne artikkelen gir en oversikt over noen av tingene som kan oppnås med refleksjon i Kotlin, inkludert både hvordan den samhandler med og skiller seg fra refleksjonsfunksjonene innebygd i standardspråket Java.

Alle eksemplene er tilgjengelige på GitHub.