読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

Kotlin Tips: Reified Type Parametersで型チェックを汎用化する

問題

Kotlin Tips : Intentに必要な値が入っていない場合例外をスローしたい - visible trueではgetLongOrThrow()をIntentに追加しlazyを使う事で可読性を高めた。

Intent.getIntOrThrow(key: String)のおさらい

fun Intent.getIntOrThrow(key: String): Int =
  this.extras.get(key).let {
    if (it !is Int) {
      throw IllegalArgumentException("Extras don't have Int value specified by key $key")
    }
    return it
  }

さて、ではStringならどうか。当然次のようなコードを追加すれば実現できる。

fun Intent.getStringOrThrow(key: String): String =
  this.extras.get(key).let {
    if (it !is String) {
      throw IllegalArgumentException("Extras don't have String value specified by key $key")
    }
    return it
  }

getIntOrThrow(key: String)とほとんど同じ実装で戻り値と型チェック部分だけが異なる。今回は以下の点にフォーカスする。

  • IntentのgetExtraの型の種類は28個ある。28回実装するのはつらい
  • getStringOrThrowなど機能的な命名を出来るだけ意図を表す命名にしたい

改善策

Reified Type Parametersを使う。Reified Type Parametersは具象化された型パラメータを利用できる機能。これによりロジック中の型チェックをTを使って行える(つまりあたかも型消去が起こらなかったかのように扱える。T::class.javaClassとかもできるよ)。

inline fun <reified T> Intent.getRequired(key: String): T {
  extras?.get(key).let {
    if (it !is T) {
        throw IllegalArgumentException("Extras don't have a value specified by key $key")
    }
    return it
  }
}

さらに利用時には型推論によって以下のように書ける。

val value:String = intent.getRequired(key)

ただしlazyを使う時は型の明示が必要となる。といっても型情報は1度しか出てこないので冗長になることはない。

val value by lazy<String> { intent.getRequired(key) }
// or
val value by lazy { intent.getRequired<String>(key) }

やったね

  • Reified Type Parametersによって複数種類の型の取り出しを汎用化できた
  • 型推論のために型はどこかに必ず宣言されるのでメソッド名に型名を含めなくてよくなった
  • Intent.getRequired(key:String)という名前によって意図が明快になった

ついでに

Intent.getOptional()も実装しておく。

inline fun <reified T> Intent.getOptional(name: String, default: T): T {
  extras?.get(name).let {
    if (it == null) {
        return default
    } else {
        return it as T
    }
  }
}

さらなる議論

Reified Type Parametersはinlineだからこそ実現できる機能である。そのためインライン展開のデメリットについては考慮したほうがよさそうだ。といってもバイトコードが膨らむくらいしか思いつかない...。