Kotlin で具体化されたキーワードを使用する

David Mbochi Njonge 2023年10月12日
  1. Java 5 より前のジェネリック
  2. Java 5 のジェネリック
  3. 型消去とは
  4. クラスをジェネリック関数に渡す
  5. Kotlin の inline 関数で reified キーワードを使用する
  6. Kotlin で reified を使用して同じ入力で関数をオーバーロードする
  7. まとめ
Kotlin で具体化されたキーワードを使用する

reified キーワードは、Kotlin でジェネリックを操作する際に主に使用されるプログラミング概念です。

アプリケーションに型安全性を追加し、明示的な型キャストを防ぐために、Java 5 でジェネリック型が導入されたことはわかっています。

ジェネリックの制限の 1つは、ジェネリック関数を操作するときに型情報にアクセスできないことです。 コンパイラは、T具体化された 型パラメーターとして使用できないと文句を言います。代わりにクラスを使用する必要があります。

この問題は、コンパイル中の型消去が原因で発生します。 このチュートリアルでは、型のクラスをジェネリック関数の引数として渡す方法と、inline 関数で reified キーワードを使用する方法を含む、2つの方法を使用してこの問題を回避する方法を学習します。

Java 5 より前のジェネリック

Java 5 より前はジェネリックが存在しなかったため、リストの実装が StringIntegerObjects、またはその他の型のリストであるかどうかを判断できませんでした。

このため、常に明示的に必要な型にキャストする必要がありました。 これらのアプリケーションは、無効なキャストに対する検証が行われていないため、RuntimeException が発生する傾向がありました。

次の例は、Java 5 より前にコレクションがどのように実装されたかを示しています。

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);
  }
}

Java 5 のジェネリック

この問題を解決するために、ジェネリックが Java 5 で導入されました。 ジェネリックを使用すると、特定のタイプのコレクションを定義できます。無効なタイプを入力しようとすると、コンパイラは警告を表示します。

コレクションから要素を取得するために明示的な型キャストが必要ないため、ジェネリックは RuntimeException の問題も解決しました。 次の例は、Java 5 以前のバージョンからコレクションがどのように実装されたかを示しています。

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);
  }
}

型消去とは

型消去は、上記の問題を解決するために Java 5 で導入された機能です。

IntelliJ に移動し、File > New > Project を選択します。 プロジェクトの name セクションに、プロジェクト名を reified-in-kotlin として入力します。

Language セクションで Kotlin を選択し、Build system セクションで Intellij を選択します。 最後に、Create ボタンを押して新しいプロジェクトを生成します。

kotlin フォルダーの下に Main.kt ファイルを作成し、次のコードをコピーしてファイルに貼り付けます。

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

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

Java 5 より前は型情報がなかったため、Java コンパイラは型情報を基本オブジェクト型とそれに必要な型キャストに置き換えました。

Java の内部で何が起こっているかを表示するには、コードを実行し、Tools > Kotlin > Show Kotlin Bytecode を押します。 開いたウィンドウで、Decompile を押して、以下に示すコンパイル済みの Java コードを表示します。

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();
  }
}

showType() メソッドに渡した型パラメーターは Object に変換されることに注意してください。これが、型情報にアクセスできない理由です。 これは、ジェネリックを使用しているときに型消去が発生する方法です。

クラスをジェネリック関数に渡す

これは、次のセクションで説明する reified キーワードを使用するほど効率的ではありませんが、型消去を回避するために使用できる最初のアプローチです。

削除されたジェネリック型にアクセスするには、以下に示すように、ジェネリック型クラスをジェネリック関数のパラメーターとして渡すことができます。

前の例についてコメントし、次のコードをコピーして Main.kt ファイルに貼り付けます。

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

このアプローチでは、ジェネリック関数を作成するたびにジェネリック型のクラスを渡す必要がありますが、これは私たちが望んでいるものではありません。

このコードを実行し、コードが以下を出力することを確認します。

class java.lang.String

Kotlin の inline 関数で reified キーワードを使用する

これは、活用できる 2 番目のアプローチであり、ジェネリックを使用して型情報にアクセスする場合に最も推奨される方法です。 reified は、Kotlin では使用できますが Java では使用できない inline 関数でのみ使用できることに注意してください。

前の例についてコメントし、次のコードをコピーして Main.kt ファイルに貼り付けます。

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

inline 関数は、reified キーワードが型情報にアクセスするのに役立ち、inline 関数本体をそれが使用されたすべての場所にコピーします。

上記のコードを実行し、上記で説明したのと同じ手順を使用して逆コンパイルします。 逆コンパイルされたコードを以下に示します。

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();
  }
}

ジェネリックパラメータに reified キーワードを使用して inline 関数 showType() を定義したため、コンパイラは関数の本体をコピーし、実際に宣言された型で置き換えたものを示す main() という final メソッドに置き換えました。

宣言された型にアクセスすると、クラスをパラメーターとして渡すことなく、ジェネリック関数から型情報を取得できます。

上記のコードを実行して、引数として渡した型パラメーターである String の型情報にアクセスできることを確認します。

class java.lang.String

Kotlin で reified を使用して同じ入力で関数をオーバーロードする

通常のジェネリック関数を操作している間、同じ入力で関数をオーバーロードして異なる型を返すことはできませんが、reified キーワードを使用してこれを実現できます。

前の例についてコメントし、次のコードをコピーして、Main.kt ファイルの後に貼り付けます。

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);
}

computeResult() メソッドを呼び出すと、コンパイラは最初の呼び出しで Int の戻り値の型を期待し、2 回目の呼び出しで Float の戻り値の型を期待します。

コンパイラは、この情報を使用して、関数の本体をコピーするときに、ジェネリック型を予期されるファイルに置き換えます。

reified を使用しているときに大きな関数を使用することは、アプリケーションのパフォーマンスの問題を引き起こすため、良い方法ではありません。 inline 関数が小さいことを確認してください。

まとめ

そのため、reified キーワードを inline 関数で使用して、Kotlin の型情報にアクセスする方法を学びました。

また、実行時に型情報が削除される原因となる型消去についても学びました。 最後に、同じ入力で reified を使用して inline 関数をオーバーロードし、異なる型を返す方法について説明しました。

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