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()) // コンパイルエラー