ジェネリクス — 基本
| 対応: | Kotlin 1.0(2016) |
|---|
Kotlinのジェネリクスは型パラメーターを使って、型安全なコレクションやアルゴリズムを定義する仕組みです。関数やクラスに型パラメーター『<T>』を付けることで、様々な型に対応できます。
構文
// ジェネリクス関数
fun <T> 関数名(引数: T): T { return 引数 }
// ジェネリクスクラス
class Box<T>(val value: T)
// 型制約(T は Comparable を実装した型のみ)
fun <T : Comparable<T>> max(a: T, b: T): T = if (a > b) a else b
// 複数の型パラメーター
fun <K, V> pair(key: K, value: V): Pair<K, V> = Pair(key, value)
構文一覧
| 構文 | 概要 |
|---|---|
| <T> | 型パラメーターの宣言です。任意の型を受け付けます。 |
| <T : 上限型> | 型制約です。T は上限型のサブタイプのみ使えます。 |
| where T : 型1, T : 型2 | 複数の型制約を指定します。 |
| Box<Int> | 型引数を指定してジェネリクスを使います。 |
| Box<*> | スター投影。型が不明な場合に使います(Java の Box<?> 相当)。 |
サンプルコード
generics_basic_kotlin.kt
// ジェネリクス関数 — 任意の型の最初の要素を返す
fun <T> firstOrNull(list: List<T>): T? = list.firstOrNull()
// 型制約付き — Comparable を実装した型の最大値を求める
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b
// ジェネリクスクラス
class Stack<T> {
private val items = mutableListOf<T>()
fun push(item: T) { items.add(item) }
fun pop(): T? = if (items.isEmpty()) null else items.removeAt(items.lastIndex)
fun peek(): T? = items.lastOrNull()
val size: Int get() = items.size
}
// where 句で複数制約を指定する
fun <T> printIfComparableAndSerializable(value: T)
where T : Comparable<T>, T : java.io.Serializable {
println(value)
}
fun main() {
println(firstOrNull(listOf(1, 2, 3))) // 1
println(firstOrNull(emptyList<String>())) // null
println(maxOf(3, 7)) // 7
println(maxOf("Kogami Shinya", "Tsunemori Akane")) // Tsunemori Akane
val stack = Stack<String>()
stack.push("a")
stack.push("b")
stack.push("c")
println(stack.pop()) // c
println(stack.peek()) // b
println(stack.size) // 2
printIfComparableAndSerializable(42) // 42
printIfComparableAndSerializable("hello") // hello
}
コンパイルして実行すると次のようになります。
kotlinc generics_basic_kotlin.kt -include-runtime -d generics_basic_kotlin.jar java -jar generics_basic_kotlin.jar 1 null 7 Tsunemori Akane c b 2 42 hello
よくあるミス
よくあるミス1: 型制約なしのジェネリクス関数で比較演算子を使うとコンパイルエラーになる。
NG例1 — Comparable 制約なしで比較演算子を使う
// T に Comparable の制約がないため > 演算子が使えない
fun <T> maxOf(a: T, b: T): T {
// return if (a > b) a else b // コンパイルエラー
return a
}
generics_basic_kotlin_ok1.kt
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b
fun main() {
println(maxOf("Kogami Shinya", "Tsunemori Akane")) // Tsunemori Akane
println(maxOf(3, 7)) // 7
}
コンパイルして実行すると次のようになります。
kotlinc generics_basic_kotlin_ok1.kt -include-runtime -d generics_basic_kotlin_ok1.jar java -jar generics_basic_kotlin_ok1.jar Tsunemori Akane 7
よくあるミス2: 『Container<String>』と『Container<Int>』は別の型であり、互いに代入できない。
NG例2 — 型引数の異なるジェネリクスインスタンスを同じ変数に代入しようとする
class Container<T>(val value: T)
fun main() {
val c1: Container<String> = Container("Kogami Shinya")
// val c2: Container<String> = Container(100) // コンパイルエラー: Int は String ではない
}
ジェネリクスクラスは型引数ごとに別の型として扱われる。代入互換性を持たせたい場合は変位指定(『out』、『in』)を使う。
よくあるミス3: 型パラメーターのデフォルト上限は『Any?』のため、『null』が渡せてしまう。
NG例3 — T のデフォルト上限が Any? なので null が入る
fun <T> printValue(v: T) { println(v) }
fun main() {
printValue<String?>(null) // null が渡せてしまう
}
generics_basic_kotlin_ok3.kt
fun <T : Any> printNonNull(v: T) { println(v) }
fun main() {
printNonNull("Tsunemori Akane")
// printNonNull(null) // コンパイルエラー
}
コンパイルして実行すると次のようになります。
kotlinc generics_basic_kotlin_ok3.kt -include-runtime -d generics_basic_kotlin_ok3.jar java -jar generics_basic_kotlin_ok3.jar Tsunemori Akane
概要
ジェネリクスを使うと型ごとに別々の関数やクラスを定義する必要がなく、型安全なコードを再利用できます。Kotlinの型推論により、多くの場合は型引数を省略できます。
型パラメーターはデフォルトで『Any?』が上限型のため、null 許容です。『<T : Any>』と書くと non-null 型のみを受け付けます。
ジェネリクスの変位指定は共変 / 反変を、実行時の型情報保持はreified 型パラメーターを参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。