KotlinのジェネリックはJavaで表現可能な仕様と、そうでないものがあります。
まずは、Javaで表現可能なものから見ていきます。
ジェネリック関数
Kotlinでジェネリック関数を宣言してみます。
// Kotlin
fun <T> convertToString(arg: T) : String {
return arg.toString()
}
通常の関数宣言のfunキーワードと関数名の間に型パラメータを宣言します。
同等の関数をJavaで表現することが出来ます。
// Java
<T> String convertToString(T arg) {
return arg.toString();
}
型制約
型パラメータに制約を持たせることが出来ます。
fun <T: Exception> convertToString(arg: T) : String {
return arg.localizedMessage
}
型パラメータTはExceptionもしくはそのサブクラスとする制約を付加しました。
こちらもJavaで表現可能です。
// Java
<T extends Exception> String convertToString(T arg) {
return arg.getLocalizedMessage();
}
ジェネリッククラス
Kotlinでジェネリッククラスを定義してみます。
class Box<T>(t: T) {
var value = t
}
クラス名とプライマリコンストラクタの間に型パラメータを宣言します。
同等のクラスをJavaで表現することが出来ます。
class Box<T> {
T value;
Box(T t) {
this.value = t;
}
}
型制約
クラスについても型パラメータに制約を持たせることが出来ます。
class Box<T: Number> (t: T) {
var value = t
}
型パラメータTについて、Numberもしくはそのサブクラスとする制約をつけました。
こちらもJavaで表現可能です。
static class Box<T extends Number> {
T value;
Box(T t) {
this.value = t;
}
}
where句
関数やクラスで型制約を複数指定する場合はwhere句が便利です。
fun <T> doSomething(t: T) where T: Runnable, T: Cancellable {
}
変数宣言と変位指定
Javaの境界ワイルドカード型と同じく、変数宣言時に変位を指定する記法がKotlinにもあります。
| Java | Kotlin | |
|---|---|---|
| 上限境界制約 | extends XXX> | <out XXX> |
| 下限境界制約 | super XXX> | <in XXX> |
共変
DogはAnimalのサブタイプです。
そこで、List<Dog>はList<Animal>のサブタイプとなるように共変の関係を作ります。
open class Animal
class Dog: Animal()
var animals: List<out Animal> = emptyList()
val dogs: List<Dog> = emptyList()
animals = dogs
反変
DogはAnimalのサブタイプです。
List<Animal>はList<Dog>のサブタイプとなるように反変の関係を作ります。
open class Animal
class Cat: Animal()
val animals: MutableList<Animal> = mutableListOf()
var cats: MutableList<in Cat> = mutableListOf()
cats = animals
Kotlinでは、宣言時の変位指定を**型投影 (type projection)**と呼びます。
クラス宣言と変位指定
Javaでは共変と反変の関係を、クラスそのものに指定することは出来ません。そのため、変数宣言などはその都度変位指定する必要があります。
一方、Kotlinにはクラスの宣言時に変位を指定する仕組みがあります。
クラスの設計者、実装者の意図に反して変位が利用されるリスクがなくなります。
// ↓型パラメータにoutポジションを指定
class Supplier<out T> {
fun get(): T {
//
}
}
Supplierクラスは型パラメータTについて共変となります。
var animalSupplier: Supplier<Animal> = Supplier()
var dogSupplier: Supplier<Dog> = Supplier()
animalSupplier = dogSupplier
変数の宣言時に変位指定が不要となります。
同様にinポジションを指定することで反変とすることも可能です。
非null許容型とジェネリック
下記のジェネリック関数、引数にはnullを渡すことが出来ます。
fun <T> doSomething(t: T){
}
doSomething(null) // OK
JavaのルートクラスObjectに相当するKotlinの型はAnyです。
が、制約のない型パラメータの上限制約はAnyではなくAny?です。
もし、非null許容型のジェネリック関数を宣言したければ
型制約にAnyを指定します。
fun <T: Any> doSomething(t: T){
}
doSomething(null) // コンパイルエラー
doSomething(1) // OK
doSomething("hoge") // OK
スター投影
型パラメータにアスタリスク*を指定するスター投影という仕組みがあります。
var animals: List<*> = //
上記のList<*>はList<Any?>と等値ではありません。List<out Any?>として振る舞います。
スター投影は型パラメータに関心がない場合に使う構文です。
要素を取得しAny?として扱う分には安全ですが、リストに要素を追加することは安全ではありません。
下記のように、想定外の型を受け取るリスクがあるためoutポジションとして振る舞います。
val animals: MutableList<*> = mutableListOf<Animal>()
val animal = animals.first() // OK
animals.add(1) // コンパイルエラー
animals.add(Dog()) // コンパイルエラー