Kotlinのreifiedとinlineについて勉強する
これは何?
ライブラリ内の実装を見るとKoltinのreifiedとinlineの組み合わせによる実装がよく見られます。この言語機能についてお勉強していきます。
JVMのType Erasure(型消去)
reified inlineを学ぶ前に、Type Erasureという機能について整理します。
これはJVM特有の話ですが、ジェネリクスにおいて型パラメータの情報はコンパイル時にのみ利用され、実行時には利用されません。これをType Erasureと言います。
fun getName(list: List<String>): String {
return list[0]
}このようなコードを書いたとします。このコードをデコンパイルしJavaに変換すると以下になります。
@NotNull
public static final String getName(@NotNull List list) {
Intrinsics.checkNotNullParameter(list, "list");
return (String)list.get(0);
}Listの型パラメータであるStringが削除されています。要素はObjectとして扱われ、取り出す際にStringにキャストされています。
このようにKotlin/JVMではジェネリクスにおいて、型パラメータはあくまでコンパイル時点に削除される、Type Erasure(型消去)という特徴があります。
Type Erasureによるコンパイルエラー
fun <T> check(value: Any): Boolean {
return value is T // Cannot check for instance of erased type 'T (of fun <T> check
}このコードはコンパイルエラーになります。なぜなら、実行時に型パラメータTを利用していますが、型パラメータTの情報は実行時には存在せず、値はObjectとして扱われるためチェックできないためです。
// コンパイル前
check<String>("hello")
// コンパイル後
check(Object value)概念的にはこのようなイメージで、実行時には型パラメータは存在しないためチェックが出来ないわけです。
reified + inline
実行時に型パラメータTを利用するコードを書きたい場合は、reifiedとinlineを組み合わせることで実現できます。
inline fun <reified T> check(value: Any): Boolean {
return value is T
}
check<String>("Hello")inline関数にすることで呼び出し元に展開します。重要な点は呼び出し元ではTの具体的な型がわかっていることです。
reifiedは具体化するという意味です。inlineで呼び出し元に展開し、型パラメータTを具体的な型に置き換える(reifiedする)ことでコンパイルが通ります。
fun main() {
val value: Any = getValue()
println(check<String>(value))
}
fun getValue(): Any = "Hello"
inline fun <reified T> check(value: Any): Boolean {
return value is T
}このようなコードをデコンパイルしJavaに変換してみます。コンパイル時の最適化されすぎないように冗長なコードを用います。
public static final void main() {
Object value = getValue();
int $i$f$check = 0;
boolean value$iv = value instanceof String;
System.out.println(value$iv);
}
@NotNull
public static final Object getValue() {
return "Hello";
}
// $FF: synthetic method
public static final boolean check(Object value) {
Intrinsics.checkNotNullParameter(value, "value");
int $i$f$check = 0;
Intrinsics.reifiedOperationMarker(3, "T");
return value instanceof Object;
}check関数はmainから呼ばれておらず、main関数で展開されています。main関数ではTがStringだと分かるため具体化されています。
まとめ
Type Erasureとreified inlineを勉強しました。
ライブラリ内のコードを読む際にreified inlineは目にしていましたが、いまいち理解できていなかったので理解できてよかったです。