Verwenden Sie das reifizierte Schlüsselwort in Kotlin

David Mbochi Njonge 12 Oktober 2023
  1. Generika vor Java 5
  2. Generika in Java 5
  3. Was ist Typenlöschung?
  4. Übergeben einer Klasse an eine generische Funktion
  5. Verwenden Sie das Schlüsselwort reified mit einer inline-Funktion in Kotlin
  6. Überladen von Funktionen mit der gleichen Eingabe mit reified in Kotlin
  7. Abschluss
Verwenden Sie das reifizierte Schlüsselwort in Kotlin

Das Schlüsselwort reified ist ein Programmierkonzept, das hauptsächlich bei der Arbeit mit Generika in Kotlin verwendet wird.

Wir wissen, dass generische Typen in Java 5 eingeführt wurden, um Typsicherheit zu Anwendungen hinzuzufügen und explizite Typumwandlungen zu verhindern.

Eine Einschränkung bei Generika besteht darin, dass Sie beim Arbeiten mit generischen Funktionen nicht auf Typinformationen zugreifen können. Der Compiler beschwert sich, dass er T nicht als reifizierten Typparameter verwenden kann und wir stattdessen eine Klasse verwenden sollten.

Dieses Problem wird durch Typlöschung während der Kompilierung verursacht. In diesem Tutorial lernen wir, wie wir dieses Problem mit zwei Ansätzen umgehen können, einschließlich der Übergabe der Klasse des Typs als Argument der generischen Funktion und der Verwendung des Schlüsselworts reified mit einer inline-Funktion.

Generika vor Java 5

Vor Java 5 gab es keine Generics, sodass wir nicht sagen konnten, ob eine Listenimplementierung eine Liste von String, Integer, Objects oder anderen Typen war.

Aus diesem Grund mussten wir immer explizit auf den gewünschten Typ umwandeln. Diese Anwendungen waren anfällig für RuntimeException, da es keine Validierungen gegen ungültige Umwandlungen gab.

Das folgende Beispiel zeigt, wie Sammlungen vor Java 5 implementiert wurden.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add("John");
    list.add(3);
    list.add(5);

    String john = (String) list.get(2);
  }
}

Generika in Java 5

Generics wurden in Java 5 eingeführt, um dieses Problem zu lösen. Generics ermöglichten es uns, einen bestimmten Sammlungstyp zu definieren, und wenn wir versuchen, einen ungültigen Typ einzugeben, zeigt der Compiler eine Warnung an.

Generics lösten auch das Problem der RuntimeException, da keine expliziten Typumwandlungen erforderlich waren, um ein Element aus der Sammlung abzurufen. Das folgende Beispiel zeigt, wie Sammlungen von Java 5 und früheren Versionen implementiert wurden.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("John");
    //        list.add(3); // Required type String
    //        list.add(true); // Required type String

    String john = list.get(0);
    System.out.println(john);
  }
}

Was ist Typenlöschung?

Typlöschung ist die Funktion, die in Java 5 eingeführt wurde, um das oben erwähnte Problem zu lösen.

Gehen Sie zu IntelliJ und wählen Sie Datei > Neu > Projekt. Geben Sie den Projektnamen als reified-in-kotlin im Abschnitt name des Projekts ein.

Wählen Sie Kotlin im Abschnitt Sprache und Intellij im Abschnitt System erstellen. Drücken Sie abschließend die Schaltfläche Erstellen, um ein neues Projekt zu erstellen.

Erstellen Sie eine Main.kt-Datei unter dem Ordner kotlin und kopieren Sie den folgenden Code und fügen Sie ihn in die Datei ein.

fun <T> showType(t: T){
    println(t);
}

fun main(){
    showType("This is a generic function");
}

Vor Java 5 gab es keine Typinformationen, daher ersetzte der Java-Compiler die Typinformationen durch den Basisobjekttyp und die erforderliche Typumwandlung.

Um zu sehen, was in Java hinter der Haube passiert, führen Sie den Code aus und drücken Sie Tools > Kotlin > Show Kotlin Bytecode. Klicken Sie im sich öffnenden Fenster auf Dekompilieren, um den unten gezeigten kompilierten Java-Code anzuzeigen.

public final class MainKt {
  public static final void showType(Object t) {
    System.out.println(t);
  }

  public static final void main() {
    showType("This is a generic function");
  }

  // $FF: synthetic method
  public static void main(String[] var0) {
    main();
  }
}

Beachten Sie, dass der Typparameter, den wir an die showType()-Methoden übergeben haben, in ein Object konvertiert wird, weshalb wir nicht auf Typinformationen zugreifen können. So erfolgt die Typlöschung bei der Arbeit mit Generika.

Übergeben einer Klasse an eine generische Funktion

Dies ist der erste Ansatz, den wir verwenden können, um das Löschen von Typen zu vermeiden, obwohl er nicht so effizient ist wie die Verwendung des Schlüsselworts reified, das wir im nächsten Abschnitt behandeln werden.

Um auf den gelöschten generischen Typ zuzugreifen, können wir die Klasse des generischen Typs als Parameter der generischen Funktion übergeben, wie unten gezeigt.

Kommentieren Sie das vorherige Beispiel und kopieren Sie den folgenden Code und fügen Sie ihn in die Datei Main.kt ein.

fun <T> showType(clazz: Class<T>){
    println(clazz);
}
fun main(){
    showType(String::class.java)
}

Bei diesem Ansatz müssen wir jedes Mal, wenn wir eine generische Funktion erstellen, eine Klasse für den generischen Typ übergeben, was nicht das ist, was wir wollen.

Führen Sie diesen Code aus und stellen Sie sicher, dass der Code Folgendes ausgibt.

class java.lang.String

Verwenden Sie das Schlüsselwort reified mit einer inline-Funktion in Kotlin

Dies ist der zweite Ansatz, den wir nutzen können, und die am meisten bevorzugte Methode beim Zugriff auf Typinformationen mithilfe von Generika. Beachten Sie, dass reified nur mit inline-Funktionen verwendet werden kann, die in Kotlin, aber nicht in Java verfügbar sind.

Kommentieren Sie das vorherige Beispiel und kopieren Sie den folgenden Code und fügen Sie ihn in die Datei Main.kt ein.

inline fun < reified T> showType(){
    println(T::class.java);
}
fun main(){
    showType<String>()
}

Die Funktion inline hilft dem Schlüsselwort reified, auf die Typinformationen zuzugreifen, indem der Funktionsrumpf inline an jede Stelle kopiert wird, an der er verwendet wurde.

Führen Sie den obigen Code aus und dekompilieren Sie ihn mit denselben Schritten, die wir oben beschrieben haben. Der dekompilierte Code ist unten dargestellt.

public final class MainKt {
  // $FF: synthetic method
  public static final void showType() {
    int $i$f$showType = 0;
    Intrinsics.reifiedOperationMarker(4, "T");
    Class var1 = Object.class;
    System.out.println(var1);
  }

  public static final void main() {
    int $i$f$showType = false;
    Class var1 = String.class;
    System.out.println(var1);
  }

  // $FF: synthetic method
  public static void main(String[] var0) {
    main();
  }
}

Da wir die inline-Funktion showType() mit dem Schlüsselwort reified im generischen Parameter definiert haben, kopierte der Compiler den Rumpf der Funktion und ersetzte ihn durch den tatsächlich deklarierten Typ, wie in der final-Methode namens main().

Der Zugriff auf die deklarierten Typen ermöglicht es uns, Typinformationen aus der generischen Funktion abzurufen, ohne die Klasse als Parameter zu übergeben.

Führen Sie den obigen Code aus, um zu überprüfen, ob er auf die Typinformationen von String zugreifen kann, dem Typparameter, den wir als Argument übergeben haben.

class java.lang.String

Überladen von Funktionen mit der gleichen Eingabe mit reified in Kotlin

Während wir mit normalen generischen Funktionen arbeiten, können wir eine Funktion nicht mit derselben Eingabe überladen und verschiedene Typen zurückgeben, aber wir können dies mit dem Schlüsselwort reified erreichen.

Kommentieren Sie das vorherige Beispiel und kopieren Sie den folgenden Code und fügen Sie ihn in die Datei nach der Datei Main.kt ein.

inline fun <reified T>computeResult(theNumber: Float): T{
    return when(T::class){
        Float::class -> theNumber as T
        Int::class -> theNumber.toInt() as T
        else -> throw IllegalStateException("")
    }
}
fun main(){
    val intResult: Int = computeResult(123643F);
    println(intResult);
    val floatResult: Float = computeResult(123643F);
    println(floatResult);
}

Wenn wir hinter den Kulissen die Methode computeResult() aufrufen, erwartet der Compiler beim ersten Aufruf einen Int-Rückgabetyp und beim zweiten Aufruf einen Float-Rückgabetyp.

Der Compiler verwendet diese Informationen, um den generischen Typ durch die erwartete Datei zu ersetzen, während der Hauptteil der Funktion kopiert wird.

Es ist keine gute Praxis, große Funktionen zu verwenden, während reified verwendet wird, da dies zu Leistungsproblemen in unseren Anwendungen führt. Stellen Sie sicher, dass Ihre inline-Funktion klein ist.

Abschluss

Wir haben also gelernt, das Schlüsselwort reified mit inline-Funktionen zu verwenden, um auf Typinformationen in Kotlin zuzugreifen.

Wir haben auch etwas über Typlöschung gelernt, die die Ursache für das Löschen von Typinformationen zur Laufzeit ist. Zuletzt haben wir behandelt, wie inline-Funktionen mit reified mit derselben Eingabe überladen werden, um verschiedene Typen zurückzugeben.

David Mbochi Njonge avatar David Mbochi Njonge avatar

David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.

LinkedIn GitHub