Reflectie met Kotlin

1. Inleiding

Reflection is de naam voor de mogelijkheid om klassen, velden en methoden tijdens runtime te inspecteren, laden en ermee te communiceren. We kunnen dit zelfs doen als we niet weten wat ze zijn tijdens het compileren.

Dit heeft een groot aantal toepassingen, afhankelijk van wat we aan het ontwikkelen zijn. Frameworks als Spring maken er bijvoorbeeld intensief gebruik van.

Ondersteuning hiervoor is ingebouwd in de JVM en is dus impliciet beschikbaar voor alle op JVM gebaseerde talen. Sommige JVM-talen hebben echter extra ondersteuning bovenop wat al beschikbaar is.

2. Java-reflectie

Alle standaard Java Reflection-constructies zijn beschikbaar en werken perfect samen met onze Kotlin-code. Dit omvat de java.lang.Class klasse en alles in de java.lang.reflect pakket.

Als we om welke reden dan ook de standaard Java Reflection API's willen gebruiken, kunnen we dat op precies dezelfde manier doen als in Java. Om bijvoorbeeld een lijst te krijgen van alle openbare methoden in een Kotlin-klasse, zouden we het volgende doen:

MyClass :: class.java.methods

Dit valt uiteen in de volgende constructies:

  • MyClass :: class geeft ons de Kotlin Class-vertegenwoordiging voor de Mijn klas klasse
  • .Java geeft ons de java.lang.Class gelijkwaardig
  • .methoden is een oproep aan de java.lang.Class.getMethods () accessor methode

Dit werkt precies hetzelfde, ongeacht of het vanuit Java of Kotlin wordt aangeroepen, en of het nu een Java- of een Kotlin-klasse is. Dit omvat Kotlin-specifieke constructies, zoals dataklassen.

data class ExampleDataClass (val name: String, var enabled: Boolean) ExampleDataClass :: class.java.methods.forEach (:: println)

Kotlin converteert de geretourneerde typen ook naar de Kotlin-representaties.

In het bovenstaande krijgen we een kotlin.Array waarop we kunnen bellen forEach ().

3. Verbeteringen in Kotlin-reflectie

Hoewel we de standaard Java Reflection API's kunnen gebruiken, is het niet op de hoogte van alle extensies die Kotlin naar het platform brengt.

Bovendien kan het in sommige situaties soms een beetje lastig zijn om te gebruiken. Kotlin brengt zijn eigen reflectie-API mee die we kunnen gebruiken om deze problemen op te lossen.

Alle toegangspunten tot de Kotlin Reflection API gebruiken referenties. Eerder zagen we het gebruik van ::klasse om een ​​verwijzing naar de Class-definitie te geven. We kunnen dit ook gebruiken om verwijzingen naar methoden en eigenschappen te krijgen.

3.1. Kotlin Class Referenties

De Kotlin Reflection API geeft toegang tot een Class-referentie. Dit kan vervolgens worden gebruikt om de volledige details van de Kotlin-klas te bekijken. Dit geeft toegang tot de Java Class-referentie - de java.lang.Class object - maar ook naar alle Kotlin-specifieke details.

De Kotlin-API voor klassendetails draait om het kotlin.reflect.KClass klasse. Dit is toegankelijk met behulp van de :: operator van een willekeurige klassenaam of instantie - bijv. String :: class.

Als alternatief kan het worden geopend door de uitbreidingsmethode te gebruiken java.lang.Class.kotlin als een Java Klasse instantie is voor ons beschikbaar:

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

Zodra we een KClass object, zijn er enkele eenvoudige dingen die het ons kan vertellen over de betreffende klasse. Sommige hiervan zijn standaard Java-concepten en andere zijn Kotlin-specifieke concepten.

We kunnen bijvoorbeeld gemakkelijk achterhalen of een klasse abstract of definitief is, maar we kunnen ook achterhalen of de klasse een dataklasse of een begeleidende klasse is:

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

We hebben ook manieren om door de klassenhiërarchie te navigeren. In Java kunnen we al overschakelen van een klasse naar de superklasse, interfaces en de buitenste klasse waarin deze is ingesloten - indien van toepassing.

Kotlin voegt hieraan toe de mogelijkheid om het Companion Object voor een willekeurige klasse te verkrijgen, en de Voorwerp instantie voor een Object-klasse:

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

We kunnen ook nieuwe instanties van een klasse maken vanuit een klasseverwijzing, op vrijwel dezelfde manier als in Java:

val listClass = ArrayList :: class val list = listClass.createInstance () assertTrue (lijst is ArrayList)

Als alternatief kunnen we toegang krijgen tot de constructors en een expliciete gebruiken als dat nodig is. Dit zijn allemaal methodeverwijzingen zoals besproken in de volgende sectie.

Op een vergelijkbare manier kunnen we toegang krijgen tot alle methoden, eigenschappen, extensies en andere leden van de klasse:

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

3.2. Referenties van Kotlin-methoden

Naast de mogelijkheid om te communiceren met klassen, we kunnen ook communiceren met methoden en eigenschappen.

Dit omvat klasse-eigenschappen - gedefinieerd met val of var, standaardklassemethoden en functies op het hoogste niveau. Net als voorheen werkt dit even goed met code die is geschreven in standaard Java als met code die is geschreven in Kotlin.

Op precies dezelfde manier als bij klassen, we kunnen een verwijzing naar een methode of eigenschap verkrijgen met behulp van de:: operator.

Dit ziet er precies hetzelfde uit als in Java 8 om een ​​methodereferentie te verkrijgen, en we kunnen het op precies dezelfde manier gebruiken. In Kotlin kan deze methodereferentie echter ook worden gebruikt om reflectie-informatie over het doel te krijgen.

Als we eenmaal een methodereferentie hebben verkregen, kunnen we deze noemen alsof het echt de methode in kwestie is. Dit staat bekend als een opvraagbare referentie:

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

We kunnen ook meer details over de methode zelf krijgen, op dezelfde manier als voor klassen. Dit omvat zowel standaard Java-details als Kotlin-specifieke details, zoals of de methode een operator of als het is in lijn:

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

Daarnaast kunnen we via deze referentie meer informatie krijgen over de inputs en outputs van de methode.

Dit omvat details over het retourtype en de parameters, inclusief Kotlin-specifieke details - zoals nullability en optionaliteit.

val str = "Hallo" 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] .isVarg) assertEquals (Charset :: class.starProjectedType, method.parameters [0] .type)

3.3. Kotlin Property Referenties

Dit werkt ook precies hetzelfde voor Eigenschappende details die kunnen worden verkregen, zijn uiteraard verschillend. Eigenschappen kunnen ons in plaats daarvan informeren of ze constanten zijn, laat geïnitialiseerd of veranderlijk:

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

Merk op dat het concept van Eigenschappen ook werkt in alle niet-Kotlin-code. Deze worden geïdentificeerd door velden die de JavaBeans-conventies volgen met betrekking tot getter- en setter-methoden.

Dit omvat klassen in de standaard Java-bibliotheek. Bijvoorbeeld de Gooibaar klasse heeft een eigenschap Gooibaar. Bericht dankzij het feit dat er een methode is getMessage () daarin gedefinieerd.

We hebben toegang tot de feitelijke eigenschap via methodeverwijzingen die worden weergegeven - de getter en setter methoden. De setter is alleen beschikbaar als we werken met een KMutableProperty - d.w.z. de eigenschap werd verklaard als var, terwijl de getter is altijd beschikbaar.

Deze worden via de krijgen() en set () methoden. De getter en setter waarden zijn werkelijke methodeverwijzingen, waardoor we er precies hetzelfde mee kunnen werken als elke andere methodeverwijzing:

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

4. Samenvatting

Dit artikel geeft een overzicht van enkele dingen die kunnen worden bereikt met reflectie in Kotlin, inclusief zowel hoe het samenwerkt met en verschilt van de reflectiemogelijkheden die zijn ingebouwd in de standaard Java-taal.

Alle voorbeelden zijn beschikbaar op GitHub.