Kotlinの型周りのちょっとした話
この記事は Aizu Advent Calendar 2021 - Adventar の20日目の記事です
特にネタがなかったので実際のプロダクトコードで罠にハマった話をします
記事にあるコードはすべてKotlinです
型パラメータ
Kotlinだとおなじみのスコープ関数の let
を例に上げてみます
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
このとき T, R
が型パラメータにあたり、コンパイル時に実際の型が当てはまります
またこのとき、 T, R
に上限境界がないため、任意な型になりえます
KotlinのNullableを型パラメータ上で表現したい場合は、上限境界を Any
にしておきましょう
fun <T> functionA(value: T): String? { return value?.toString() } fun <T : Any> functionB(value: T?): String? { return value?.toString() }
型消去とreified
上記の型パラメータは型消去があるため実行時には基本的に参照することができません
一応Kotlinには reified
があるため実行時に参照することが可能です
inline fun <reified T> function(value: T): Boolean { return value is T }
reified
を使うと以下のことができます
- is, asなどの型チェック、キャスト
- ::class, ::class.javaなどのリフレクション、型クラス取得
- 別の関数への型パラメータとしての利用
注意点として inline fun
なのでインライン展開されるのを意識してください
(例えばJavaから呼び出しとかはできないです)
罠にハマった話
ここがこの記事を書こうと思った本題です サンプルコードなので命名や実装は適当です🙏
open class C() open class D() : C() open class A<T : C>(vararg value: T) { val clazz = value::class.java.componentType } class B<T : C>() : A<T>() fun main() { val a = A<D>() val b = B<D>() println(a.clazz) // class D println(b.clazz) // class C }
脳死で書いていたら無事罠にかかりまして、b.clazz
はDではなくCなんですよね
reified
を使って回避しようにも、コンストラクタに使えないのでダメですね
色々解決策はありましたが、結構コード自体闇が深くなりそうなので結果的には全く別の実装をしました
戒めとして記事に残します