Reflexion mit Kotlin

1. Einleitung

Reflection ist der Name für die Fähigkeit, Klassen, Felder und Methoden zur Laufzeit zu überprüfen, zu laden und mit ihnen zu interagieren. Wir können dies auch dann tun, wenn wir nicht wissen, was sie zur Kompilierungszeit sind.

Dies hat eine große Anzahl von Anwendungen, je nachdem, was wir entwickeln. Zum Beispiel machen Frameworks wie Spring davon stark Gebrauch.

Die Unterstützung hierfür ist in die JVM integriert und somit implizit für alle JVM-basierten Sprachen verfügbar. Einige JVM-Sprachen bieten jedoch zusätzliche Unterstützung zusätzlich zu den bereits verfügbaren.

2. Java-Reflexion

Alle Standardkonstrukte von Java Reflection sind verfügbar und funktionieren perfekt mit unserem Kotlin-Code . Dies umfasst die Klasse java.lang.Class sowie alles im Paket java.lang.reflect .

Wenn wir aus irgendeinem Grund die Standard-Java-Reflection-APIs verwenden möchten, können wir dies genauso tun wie in Java. Um beispielsweise eine Liste aller öffentlichen Methoden in einer Kotlin-Klasse zu erhalten, gehen wir wie folgt vor:

MyClass::class.java.methods

Dies gliedert sich in folgende Konstrukte:

  • Die MyClass :: -Klasse gibt uns die Kotlin- Klassendarstellung für die MyClass- Klasse
  • .java gibt uns das Äquivalent java.lang.Class
  • .methods ist ein Aufruf an die java.lang.Class.getMethods () Accessormethode

Dies funktioniert genauso, unabhängig davon, ob es von Java oder Kotlin aufgerufen wird und ob es von einer Java- oder einer Kotlin-Klasse aufgerufen wird . Dies schließt Kotlin-spezifische Konstrukte wie Datenklassen ein.

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

Kotlin konvertiert die zurückgegebenen Typen ebenfalls in die Kotlin-Darstellungen.

Oben erhalten wir ein kotlin.Array, auf dem wir forEach () aufrufen können .

3. Verbesserungen der Kotlin-Reflexion

Wir können zwar die Standard-Java Reflection-APIs verwenden, es sind jedoch nicht alle Erweiterungen bekannt, die Kotlin für die Plattform bereitstellt .

Außerdem kann es in manchen Situationen gelegentlich etwas umständlich sein, es zu verwenden. Kotlin bringt eine eigene Reflection-API mit, die wir verwenden können, um diese Probleme zu lösen.

Alle Einstiegspunkte in die Kotlin Reflection-API verwenden Referenzen. Früher haben wir die Verwendung von :: class gesehen , um einen Verweis auf die Klassendefinition zu geben. Wir können dies auch verwenden, um Verweise auf Methoden und Eigenschaften zu erhalten.

3.1. Kotlin Klassenreferenzen

Die Kotlin Reflection API ermöglicht den Zugriff auf eine Klassenreferenz. Dies kann dann verwendet werden, um die vollständigen Details der Kotlin-Klasse zu überprüfen . Dies ermöglicht den Zugriff auf die Java-Klassenreferenz - das Objekt java.lang.Class - aber auch auf alle Kotlin-spezifischen Details.

Die Kotlin-API für Klassendetails konzentriert sich auf die Klasse kotlin.reflect.KClass . Auf diesen kann mit dem Operator :: von einem beliebigen Klassennamen oder einer beliebigen Instanz aus zugegriffen werden - z . B. String :: class.

Alternativ kann sie unter Verwendung der Extension - Methode zugegriffen werden java.lang.Class.kotlin wenn eine Java - Klasse Instanz uns zur Verfügung steht:

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

Sobald wir ein KClass- Objekt erhalten haben, gibt es einige einfache Dinge, die es uns über die betreffende Klasse erzählen kann . Einige davon sind Standard-Java-Konzepte, andere sind Kotlin-spezifische Konzepte.

Zum Beispiel können wir leicht herausfinden, ob eine Klasse abstrakt oder endgültig ist, aber wir können auch herausfinden, ob die Klasse eine Datenklasse oder eine Begleitklasse ist:

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

Wir haben auch Möglichkeiten, uns in der Klassenhierarchie zu bewegen. In Java können wir bereits von einer Klasse zu ihrer Oberklasse, ihren Schnittstellen und der äußeren Klasse wechseln, in der sie eingeschlossen ist - falls zutreffend.

Kotlin fügt hinzu, dass das Companion-Objekt für eine beliebige Klasse und die Object- Instanz für eine Object-Klasse abgerufen werden können:

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

Wir können neue Instanzen einer Klasse auch aus einer Klassenreferenz erstellen , ähnlich wie in Java:

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

Alternativ können wir auf die Konstruktoren zugreifen und bei Bedarf einen expliziten verwenden. Dies sind alles Methodenreferenzen, wie im nächsten Abschnitt erläutert.

Auf sehr ähnliche Weise können wir auf alle Methoden, Eigenschaften, Erweiterungen und andere Mitglieder der Klasse zugreifen:

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

3.2. Kotlin-Methodenreferenzen

Wir können nicht nur mit Klassen interagieren, sondern auch mit Methoden und Eigenschaften .

Dies umfasst Klasseneigenschaften - definiert mit val oder var , Standardklassenmethoden und Funktionen der obersten Ebene. Nach wie vor funktioniert dies bei Code, der in Standard-Java geschrieben wurde, genauso gut wie bei Code, der in Kotlin geschrieben wurde.

Genauso wie bei Klassen können wir mit dem Operator :: einen Verweis auf eine Methode oder Eigenschaft erhalten .

Dies sieht genauso aus wie in Java 8, um eine Methodenreferenz zu erhalten, und wir können sie genauso verwenden. In Kotlin kann diese Methodenreferenz jedoch auch verwendet werden, um Reflexionsinformationen über das Ziel zu erhalten.

Sobald wir eine Methodenreferenz erhalten haben, können wir sie so aufrufen, als wäre sie wirklich die fragliche Methode . Dies wird als aufrufbare Referenz bezeichnet:

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

We can also get more details about the method itself, in the same way, that we can for classes. This includes both standard Java details as well as Kotlin specific details such as if the method is an operator or if it's inline:

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

In addition to this, we can get more information about the inputs and outputs of the method through this reference.

This includes details about the return type and the parameters, including Kotlin specific details – such as nullability and optionality.

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 Property References

This works exactly the same for Properties as well, though obviously, the details that can be obtained are different. Properties instead can inform us if they are constants, late initialized or mutable:

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

Note that the concept of Properties also works in any non-Kotlin code. These are identified by fields that follow the JavaBeans conventions regarding getter and setter methods.

This includes classes in the Java standard library. For example, the Throwable class has a Property Throwable.message by virtue of the fact that there is a method getMessage() defined in it.

We can access the actual Property through Method references that are exposed – the getter and setter methods. The setter is only available if we are working with a KMutableProperty – i.e. the property was declared as var, whereas the getter is always available.

These are exposed in an easier to use way via the get() and set() methods. The getter and setter values are actual method references, allowing us to work with them exactly the same as any other method reference:

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. Summary

Dieser Artikel gibt einen Überblick über einige Dinge, die mit Reflection in Kotlin erreicht werden können, einschließlich der Art und Weise, wie es mit den in die Standard-Java-Sprache integrierten Reflektionsfunktionen interagiert und sich von diesen unterscheidet.

Alle Beispiele sind auf GitHub verfügbar.