1. KClass und ::class: der Einstieg in Reflection

[THEORIE]

Bisher kennst du beim Schreiben von Code immer den genauen Typ eines Werts — Kunde, Bestellung, Produkt — und rufst dessen Properties und Methoden direkt auf. Manche Aufgaben lassen sich damit nicht lösen: Ein Test-Framework, das für jede beliebige Klasse eines Projekts prüfen soll, welche Funktionen es gibt, kennt diese Klasse beim Schreiben des Frameworks noch nicht. Ein generisches Logging-Werkzeug, das automatisch alle Felder eines Objekts ausgeben soll, ebenso wenig.

Reflection ist die Fähigkeit eines Programms, die Struktur seiner eigenen Typen zur Laufzeit zu untersuchen (zu introspizieren), statt sie beim Kompilieren fest vorzugeben. In Kotlin ist der Einstiegspunkt dafür die Klasse KClass, die man über die Class-Literal-Syntax ::class bekommt:

class Kunde(val name: String, val email: String)

fun main() {
    val kunde = Kunde("Anna Weber", "anna@example.com")
    val typ = kunde::class

    println(typ.simpleName)    // Kunde
    println(typ.qualifiedName) // (Paketname).Kunde
}

Deklarierter Typ vs. Laufzeit-Typ

Ein wichtiger Unterschied: Kunde::class (direkt an der Klasse) und kunde::class (an einer Instanz) liefern nicht zwangsläufig dasselbe. Bei Vererbung liefert instanz::class immer die tatsächliche Klasse zur Laufzeit — unabhängig davon, als welcher Typ die Variable deklariert ist:

open class Mitarbeiter(val name: String)
class Manager(name: String, val teamgroesse: Int) : Mitarbeiter(name)

fun main() {
    val chef: Mitarbeiter = Manager("Julia Bauer", 8)
    println(chef::class.simpleName) // Manager, nicht Mitarbeiter
}

chef ist deklariert als Mitarbeiter, enthält aber ein Manager-Objekt. Das ist dieselbe Unterscheidung, die auch hinter Smart Casts mit is steckt — nur bekommst du hier ein KClass-Objekt, das du weiterreichen und untersuchen kannst, statt nur einen Boolean-Check.

Interop mit Java: .java

Manche Java-APIs erwarten kein KClass, sondern ein java.lang.Class. Dafür gibt es die Property .java:

val javaTyp: Class<Kunde> = Kunde::class.java

Die kotlin-reflect-Abhängigkeit

::class selbst ist ein Sprachfeature und funktioniert immer, ohne zusätzliche Abhängigkeit. Für alles, was über die reine Referenz hinausgeht — Eigenschaften wie isData oder isFinal, und erst recht Listen von Properties und Funktionen, die in den nächsten Lektionen folgen — brauchst du die separate Runtime-Bibliothek kotlin-reflect. Sie ist bewusst nicht Teil der Standard Library, damit Anwendungen, die keine Reflection nutzen, keine unnötige Größe mitschleppen.

Mit Gradle (Kotlin DSL) bindest du sie so ein:

dependencies {
    implementation(kotlin("reflect"))
}

Fehlt sie, wirft dein Programm typischerweise eine KotlinReflectionNotSupportedError, sobald du mehr als die reine Referenz brauchst. Ab jetzt gilt für den gesamten Kurs: Diese Abhängigkeit muss im Projekt vorhanden sein.

Ein paar grundlegende Eigenschaften von KClass, die schon in dieser Lektion nützlich sind:

data class Rechnung(val betrag: Double)
open class Basis

println(Rechnung::class.isData) // true
println(Basis::class.isFinal)   // true (nicht als open deklariert)

Weiterlesen

[VORHERSAGE]

Lies den Code genau durch, bevor du ihn ausführst.

vorhersage.kt
open class Fahrzeug(val kennzeichen: String)
class Lieferwagen(kennzeichen: String, val ladung: Int) : Fahrzeug(kennzeichen)

fun beschreibe(fahrzeug: Fahrzeug) {
    println(fahrzeug::class.simpleName)
}

fun main() {
    val fuhrpark: List<Fahrzeug> = listOf(
        Fahrzeug("B-AB 123"),
        Lieferwagen("B-CD 456", 500)
    )
    fuhrpark.forEach { beschreibe(it) }
}

beschreibe() nimmt ein Fahrzeug entgegen. Gibt sie für beide Listenelemente "Fahrzeug" aus, oder unterscheidet sich die Ausgabe?

[AUFGABE]

Ein Lagerverwaltungssystem soll für ein beliebiges Objekt protokollieren, mit welcher Klasse es es zu tun hat. Schreibe eine Funktion klassenName(objekt: Any): String, die den simpleName der tatsächlichen Klasse von objekt zurückgibt — oder "Unbekannt", falls simpleName null ist (kann bei anonymen Objekten vorkommen). Rufe sie in main() für ein Produkt und ein Ersatzteil auf.

aufgabe.kt
open class Produkt(val name: String, val preis: Double) class Ersatzteil(name: String, preis: Double, val seriennummer: String) : Produkt(name, preis) // TODO: fun klassenName(objekt: Any): String fun main() { val produkt: Produkt = Produkt("Schraubendreher", 4.99) val ersatzteil: Produkt = Ersatzteil("Zahnrad", 12.50, "ZR-2201") // TODO: println(klassenName(produkt)) // TODO: println(klassenName(ersatzteil)) }

Bevor du einen Hint bekommst: Was hast du schon probiert, und woran hängt es genau?

[VERSTÄNDNIS-CHECK]

1. Was liefert instanz::class, wenn instanz vom deklarierten Typ Basisklasse ist, aber tatsächlich eine Unterklasse enthält?

2. Welche zusätzliche Bibliothek braucht man, um die meisten KClass-Features (z. B. memberProperties, die in einer späteren Lektion vorkommen) zur Laufzeit zu nutzen?

3. Was liefert Kunde::class.java?