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

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

Kotlinの総称型(ジェネリクス) 後編

Kotlin

今回の記事は前回に引き続き、ことりんの総称型についてです。
Javaの問題点を克服した造りになっており非常に便利に感じます。

※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリは参考サイトの翻訳をベースに、加筆・変更を施した構成となっています。

参考サイト http://confluence.jetbrains.net/display/Kotlin/Generics

ジェネリック関数

クラスだけではなく、関数も型パラメータを持てます。通常、型パラメータは関数名の後に山括弧(<>←これ)の中に置けます。

fun foo<T>(t : T) : T {
  //・・・
}

しかし拡張関数では、レシーバの型の指定の前に型パラメータを指定する必要があります。Kotlinではもうひとつの構文も許可しています。

fun <T> Int.foo(t : T) : T {
  //・・・
}

明示的に型パラメータを渡したい場合、関数名の後にそれを指定して呼び出します。

val str = foo<String>("abc")

ジェネリック制約

与えられた型パラメータの代用となり得る型の集合は、ジェネリック制約によって制限されることがあります。

上限

最も一般的な制約は、Javaのextendsキーワードに対応する上限(upper bound)です。

fun sort<T : Comparable<T>>(list : List<T>) {
  // ...
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

コロンの後に指定されている型が上限です。つまりTの代わりになれるのはComparableの派生型だけです。例えば

sort(list(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(list(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

デフォルト(何も指定されていないとき)の上限は Any? です。山括弧の中に複数の型パラメータは指定できません。同一型パラメータが複数の上限を必要とする場合、where節で区切ります。

fun cloneWhenGreater<T : Comparable<T>>(list : List<T>, threshold : T) : List<T>
    where T : Cloneable {
  return list when {it > threshold} map {it.clone()}
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

クラスオブジェクト

その他のジェネリック制約は、クラスオブジェクトの制約です。それは、Tの代わりになる型のルートクラスのクラスオブジェクトのプロパティを制限します。次の例を考えてください。Defaultクラスがあると仮定します。このクラスは、自身のデフォルト値を保持するdefaultプロパティを持っています。

abstract class Default<T> {
  val default : T
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

例えばIntクラスは次のようにDefaultクラスを拡張できるでしょう。

class Int {
  class object : Default<Int> {
    override val default = 0
  }
  // ...
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

では、複数のnull許容なT(つまり T?)を取り、すべてのnullをデフォルト値に置換するような関数を考えましょう。

fun replaceNullsWithDefaults<T : Any>(list : List<T?>) : List<T> {
  return list map {
    if (it == null)
      T.default // For now, we don't know if T's class object has such a property
    else it
  }
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

この関数をコンパイルするためには、TのクラスオブジェクトがDefaultの派生型となるように要求する型制約を指定する必要があります。

fun replaceNullsWithDefaults<T : Any>(list : List<T?>) : List<T>
    where class object T : Default<T> {
// ...

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

今、コンパイラはTが(クラスオブジェクトの参照として)defaultプロパティを持っていることを知っているので、アクセスが出来るようになります。Kotlinの総称型は実行時に保持されるので、これが可能となります。

実行時型情報

Kotlinは実行時に、型や関数のジェネリック引数を含む型情報を保存します。

この情報は typeinfo()関数によって取得できます。

val typeinfo = typeinfo(list)

http://confluence.jetbrains.net/display/Kotlin/Runtime+Type+Information より引用

typeinfo()は 型情報と、与えられた型のメンバへ自己反映的なアクセスを提供するTypeInfoクラスのオブジェクトを返します。

typeinfo()関数のオーバロードバージョンを使えますが、型引数に渡します。

val typeinfo = typeinfo<List<Int>>

http://confluence.jetbrains.net/display/Kotlin/Runtime+Type+Information より引用