Kotlin ジェネリクス

ジェネリクスとは、すなわち「パラメータ化された型」であり、型をパラメータ化して、クラス、インターフェース、メソッドで使用できます。

Java と同様に、Kotlin もジェネリクスを提供し、型安全性を保証して、型キャストの煩わしさを解消します。

ジェネリクスクラスを宣言する:

class Box<T>(t: T) {
    var value = t
}

クラスのインスタンスを作成する際に、型パラメータを指定する必要があります:

val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。

次の例では、ジェネリクスクラス Box に整数データと文字列を渡します:

class Box<T>(t : T) {
    var value = t
}

fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("サンプル")

    println(boxInt.value)
    println(boxString.value)
}

出力結果は次のとおりです:

10
サンプル

ジェネリクス型変数を定義する場合、型パラメータを完全に記述できます。コンパイラが型パラメータを自動的に推定できる場合は、型パラメータを省略することもできます。

Kotlin のジェネリクス関数の宣言は Java と同じで、型パラメータは関数名の前に置きます:

fun <T> boxIn(value: T) = Box(value)

// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // 编译器会进行类型推断

ジェネリクス関数を呼び出す際に、型パラメータを推論できる場合は、ジェネリクスパラメータを省略できます。

次の例では、ジェネリクス関数 doPrintln を作成し、渡された異なる型に応じて対応する処理を行います:

fun main(args: Array<String>) {
    val age = 23
    val name = "サンプル"
    val bool = true

    doPrintln(age)    // 整型
    doPrintln(name)   // 字符串
    doPrintln(bool)   // 布尔型
}

fun <T> doPrintln(content: T) {

    when (content) {
        is Int -> println("整型数字为 $content")
        is String -> println("字符串转换为大写:${content.toUpperCase()}")
        else -> println("T 不是整型,也不是字符串")
    }
}

出力結果は次のとおりです:

整型数字为 23
字符串转换为大写:サンプル
T 不是整型,也不是字符串

ジェネリクス制約

ジェネリクス制約を使用して、指定されたパラメータで使用できる型を設定できます。

Kotlin では : を使用して、ジェネリクスの型の上限を制約します。

最も一般的な制約は上界(upper bound)です:

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

Comparableのサブタイプは T の代わりに使用できます。 例:

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型

デフォルトの上界は Any? です。

複数の上界制約条件がある場合は、where 句を使用できます:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

変性

Kotlin にはワイルドカード型はありません。代わりに、宣言サイト変性(declaration-site variance)と型投影(type projections)の2つがあります。

宣言サイト変性

宣言サイトの型変異には、共変アノテーション修飾子:in、out を使用します。消費者は in、生産者は out です。

out を使用すると型パラメータは共変になります。共変の型パラメータは出力としてのみ使用でき、戻り値の型としては使用できますが、引数の型としては使用できません:

// 定义一个支持协变的类
class サンプル<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}

fun main(args: Array<String>) {
    var strCo: サンプル<String> = サンプル("a")
    var anyCo: サンプル<Any> = サンプル<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // 输出 a
}

in を使用すると型パラメータは反変になります。反変の型パラメータは入力としてのみ使用でき、引数の型としては使用できますが、戻り値の型としては使用できません:

// 定义一个支持逆变的类
class サンプル<in A>(a: A) {
    fun foo(a: A) {
    }
}

fun main(args: Array<String>) {
    var strDCo = サンプル("a")
    var anyDCo = サンプル<Any>("b")
    strDCo = anyDCo
}

スター投影

場合によっては、型パラメータについて何も知らないことを示したいが、それでも安全に使用したいことがあります。ここでいう「安全に使用する」とは、ジェネリクス型に対して型投影を定義し、そのジェネリクス型のすべての具象インスタンスがこの投影のサブタイプであることを要求することを意味します。

この問題に対して、Kotlin はスター投影(star-projection)と呼ばれる構文を提供しています:

  • 型が Foo<out T> と定義されているとします。ここで T は共変の型パラメータで、上界(upper bound)は TUpper です。Foo<> は Foo<out TUpper> と同等です。これは、T が不明な場合、Foo<> から TUpper 型の値を安全に読み取れることを意味します。
  • 型が Foo<in T> と定義されているとします。ここで T は反変の型パラメータです。Foo<> は Foo<in Nothing> と同等です。これは、T が不明な場合、Foo<> に何も安全に書き込めないことを意味します。
  • 型が Foo<T> と定義されているとします。ここで T は共変の型パラメータで、上界(upper bound)は TUpper です。値を読み取る場合、Foo<*> は Foo<out TUpper> と同等であり、値を書き込む場合、Foo<in Nothing> と同等です。

ジェネリクス型に複数の型パラメータが存在する場合、各型パラメータを個別に投影できます。たとえば、型が interface Function<in T, out U> と定義されている場合、次のようないくつかのスター投影が可能です:

  1. Function<*, String> は Function<in Nothing, String> を表します;
  2. Function<Int, *> は Function<Int, out Any?> を表します;
  3. Function<, > は Function<in Nothing, out Any?> を表します。

注意: スター投影は Java の raw 型(raw type)と非常に似ていますが、安全に使用できます。

その他の拡張