Kotlin で具体化されたキーワードを使用する
- Java 5 より前のジェネリック
- Java 5 のジェネリック
- 型消去とは
- クラスをジェネリック関数に渡す
-
Kotlin の
inline
関数でreified
キーワードを使用する -
Kotlin で
reified
を使用して同じ入力で関数をオーバーロードする - まとめ
reified
キーワードは、Kotlin でジェネリックを操作する際に主に使用されるプログラミング概念です。
アプリケーションに型安全性を追加し、明示的な型キャストを防ぐために、Java 5 でジェネリック型が導入されたことはわかっています。
ジェネリックの制限の 1つは、ジェネリック関数を操作するときに型情報にアクセスできないことです。 コンパイラは、T
を 具体化された
型パラメーターとして使用できないと文句を言います。代わりにクラスを使用する必要があります。
この問題は、コンパイル中の型消去が原因で発生します。 このチュートリアルでは、型のクラスをジェネリック関数の引数として渡す方法と、inline
関数で reified
キーワードを使用する方法を含む、2つの方法を使用してこの問題を回避する方法を学習します。
Java 5 より前のジェネリック
Java 5 より前はジェネリックが存在しなかったため、リストの実装が String
、Integer
、Objects
、またはその他の型のリストであるかどうかを判断できませんでした。
このため、常に明示的に必要な型にキャストする必要がありました。 これらのアプリケーションは、無効なキャストに対する検証が行われていないため、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 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