Wie funktioniert das verifizierte Schlüsselwort in Kotlin?


144

Ich versuche, den Zweck des reifiedSchlüsselworts zu verstehen. Anscheinend können wir damit über Generika nachdenken .

Wenn ich es jedoch weglasse, funktioniert es genauso gut. Möchte jemand erklären, wann dies tatsächlich einen Unterschied macht ?


Sind Sie sicher, dass Sie wissen, was Reflexion ist? Haben Sie auch von Typlöschung gehört?
Mibac

3
Generische Typparameter werden zur Laufzeit gelöscht. Lesen Sie Informationen zum Löschen von Typen, falls Sie dies noch nicht getan haben. Überarbeitete Typparameter für Inline-Funktionen inline nicht nur den Methodenkörper, sondern auch den generischen Typparameter , mit dem Sie beispielsweise T :: class.java ausführen können (was mit normalen generischen Typen nicht möglich ist). Als Kommentar setzen, weil ich momentan keine Zeit habe, eine vollständige Antwort auszuarbeiten.
F. George

Es ermöglicht den Zugriff auf den konkreten generischen Typ einer Funktion, ohne sich auf Reflexion zu verlassen und ohne den Typ als Argument übergeben zu müssen.
BladeCoder

Antworten:


367

TL; DR: Wofür ist das reifiedgut ?

fun <T> myGenericFun(c: Class<T>) 

Im Hauptteil einer generischen Funktion wie myGenericFunkönnen Sie nicht auf den Typ zugreifen, Tda er nur zur Kompilierungszeit verfügbar ist, aber zur Laufzeit gelöscht wird. Wenn Sie den generischen Typ als normale Klasse im Funktionskörper verwenden möchten, müssen Sie die Klasse daher explizit als Parameter übergeben, wie in gezeigt myGenericFun.

Wenn Sie jedoch eine inlineFunktion mit einem Reified erstellen T, kann auf den Typ von Tauch zur Laufzeit zugegriffen werden, sodass Sie die Funktion nicht Class<T>zusätzlich übergeben müssen. Sie können damit arbeiten, Tals wäre es eine normale Klasse, z. B. möchten Sie möglicherweise überprüfen, ob eine Variable eine Instanz von ist T , was Sie dann einfach tun können:myVar is T .

Eine solche inlineFunktion mit reifiedTyp Tsieht wie folgt aus:

inline fun <reified T> myGenericFun()

Wie reified funktioniert

Sie können nur reifiedin Kombination mit einer inlineFunktion verwenden . Durch eine solche Funktion kopiert der Compiler den Bytecode der Funktion an jede Stelle, an der die Funktion verwendet wird (die Funktion wird "inline"). Wenn Sie eine Inline-Funktion mit reifiziertem Typ aufrufen, kennt der Compiler den tatsächlichen Typ, der als Typargument verwendet wird, und ändert den generierten Bytecode, um die entsprechende Klasse direkt zu verwenden. Daher werden Aufrufe wie myVar is Twerden myVar is String(wenn das Typargument wäre String) im Bytecode und zur Laufzeit.


Beispiel

Schauen wir uns ein Beispiel an, das zeigt, wie hilfreich es sein reifiedkann. Wir möchten eine Erweiterungsfunktion für Stringaufgerufene erstellen toKotlinObject, die versucht, eine JSON-Zeichenfolge in ein einfaches Kotlin-Objekt mit einem Typ zu konvertieren, der durch den generischen Typ der Funktion angegeben wird T. Wir können com.fasterxml.jackson.module.kotlindies verwenden und der erste Ansatz ist der folgende:

a) Erster Ansatz ohne reifizierten Typ

fun <T> String.toKotlinObject(): T {
      val mapper = jacksonObjectMapper()
                                                        //does not compile!
      return mapper.readValue(this, T::class.java)
}

Die readValueMethode verwendet einen Typ, den sie analysieren JsonObjectsoll. Wenn wir versuchen, den ClassParameter type zu erhalten T, beschwert sich der Compiler: "'T' kann nicht als reifizierter Typparameter verwendet werden. Verwenden Sie stattdessen eine Klasse."

b) Problemumgehung mit expliziten ClassParametern

fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, c.java)
}

Als Problemumgehung kann das Classof Tzu einem Methodenparameter gemacht werden, der dann als Argument für verwendet wird readValue. Dies funktioniert und ist ein allgemeines Muster im generischen Java-Code. Es kann wie folgt aufgerufen werden:

data class MyJsonType(val name: String)

val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)

c) Der Kotlin-Weg: reified

Die Verwendung einer inlineFunktion mit reifiedTypparameter Termöglicht es, die Funktion anders zu implementieren:

inline fun <reified T: Any> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, T::class.java)
}

Es gibt keine Notwendigkeit , das nehmen Classvon Tzusätzlich, Tkann verwendet werden , als ob es sich um eine normale Klasse. Für den Client sieht der Code folgendermaßen aus:

json.toKotlinObject<MyJsonType>()

Wichtiger Hinweis: Arbeiten mit Java

Eine Inline-Funktion mit reifiedTyp kann nicht über Java- Code aufgerufen werden.


5
Vielen Dank für Ihre umfassende Antwort! Das macht eigentlich Sinn. Ich frage mich nur, warum Reified benötigt wird, wenn die Funktion trotzdem eingebunden wird. Es würde den Typ löschen und die Funktion trotzdem inline? Dies scheint mir eine Verschwendung zu sein. Wenn Sie die Funktion einbinden, können Sie auch den verwendeten Typ einbinden, oder sehe ich hier etwas falsches?
hl3mukkel

5
Vielen Dank für Ihr Feedback. Eigentlich vergesse ich, etwas zu erwähnen, das Ihnen die Antwort geben könnte: Eine normale Inline-Funktion kann von Java aus aufgerufen werden , eine mit einem reifizierten Typparameter jedoch nicht! Ich denke, dies ist ein Grund, warum nicht jeder Typparameter einer Inline-Funktion automatisch geändert wird.
s1m0nw1

Was ist, wenn die Funktion eine Mischung aus reifizierten und nicht reifizierten Parametern ist? Das macht es sowieso nicht berechtigt, von Java aufgerufen zu werden. Warum nicht alle Typparameter automatisch überprüfen? Warum muss kotlin für alle Typparameter explizit reified angegeben haben?
Vairavan

1
Was ist, wenn obere Aufrufer im Stapel nicht json.toKotlinObject <MyJsonType> (), sondern json.toKotlinObject <T> () für verschiedene Objekte benötigen?
sieben

1

EINFACH

* reified ist die Erlaubnis zur Verwendung zur Kompilierungszeit zu geben (um auf T inside de function zuzugreifen)

z.B:

 inline fun <reified T:Any>  String.convertToObject(): T{

    val gson = Gson()

    return gson.fromJson(this,T::class.java)

}

mit like:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.