ジェネリック
プライマリコンストラクタで受け取った文字列をプロパティとして持つオブジェクトを作成し、
その文字列を取得するコードは以下の通り。普通。
1 2 3 4 5 6 7 |
class MyObject(var value: String) { fun getProp(): String { return this.value } } val m = MyObject("hogehoge") println(m.getProp()) |
この書き方だと、型が文字列に限定される。
型を一般化して、文法で型と紐づける書式がジェネリック。
上記のコードをジェネリックで一般化してみる。
一般化した型引数をTとしている。
1 2 3 4 5 6 7 8 9 |
class MyGenerics<T>(var value: T) { fun getProp(): T { return this.value } } val g1 = MyGenerics<String>("hogehoge") println(g1.getProp()) // hogehoge val g2 = MyGenerics<Int>(100) println(g2.getProp()) // 100 |
型推論による型指定の省略
プライマリコンストラクタに渡すパラメタの型からジェネリック型が唯一決まる場合は、
インスタンス化時の型指定を省略できる。
1 2 3 4 5 6 7 8 9 |
class MyGenerics<T>(var value: T) { fun getProp(): T { return this.value } } val g1 = MyGenerics<String>("hogehoge") println(g1.getProp()) // hogehoge val g2 = MyGenerics("fugafuga") println(g2.getProp()) // fugafuga |
複数のジェネリック型指定
複数のジェネリック型を指定できる。下記のような書き方になる。
1 2 3 4 5 6 7 8 9 10 11 |
class MyGenerics2<T,R>(var value1: T, var value2: R) { fun getProp1(): T { return this.value1 } fun getProp2(): R { return this.value2 } } val g3 = MyGenerics2<Int,String>(100,"hoge") println(g3.getProp1()) println(g3.getProp2()) |
型引数の制約
型引数TをT:typeとすると、type型かtype型の派生型という意味になり、型の制約を作れる。
ジェネリック型をHogeクラスの派生に制限する定義として、インスタンス化時にInt型を指定する例。
コンパイル時(前)にちゃんとエラーが出る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Hoge(var def: String) { fun getVal(): String{ return "hoge" } } class MyGenerics3<T:Hoge>(var value: T) { fun getProp(): T { return this.value } } // Error:(44, 32) Kotlin: The integer literal does not conform to the expected type Hoge var g4 = MyGenerics3<Hoge>(100) println(g4.getProp()) val hoge = Hoge("hoge") var g5 = MyGenerics3<Hoge>(hoge) println(g5.getProp().getVal()) |
ジェネリック関数
関数の引数と戻り値を関数の呼び出し時に決めて紐づけられる仕組み。
ジェネリック型の定義はclassだが、ジェネリック関数はclassではなく関数。
以下は、リストの末尾(tail)を応答するジェネリック関数。
引数として配列を取るが、配列の型を呼び出し時に決められる。
1 2 3 |
fun <T> tail(list: Array<T>): T = list[list.size-1] var data = arrayOf(1,2,3,4,5) println(tail(data)) |
共変(covariant)と不変(invariant)
Javaの話。Javaでは配列の要素のクラスに継承関係があるとき、
その配列は親クラスの配列に代入できる。
aaryの定義はNumberの配列だが中身はIntegerの配列になっている。
定義上Numberの配列だから、Numberの派生クラスのインスタンスを代入する式を書いてもコンパイルが通る。
例えば、Longのインスタンスを入れられる。
実態はIntegerの配列だから、Longのインスタンスを入れた瞬間に怒られる。
1 2 3 |
Integer[] iary = new Integer[10]; Number[] aary = iary; aary[0] = new Long(0); // Exception occured. |
kotolinの配列は共変ではなく不変。
配列の要素に継承関係があったとしても代入できない。
これを不変(invariant)と言ってkotolinは不変が基本。
1 2 |
var intArray: Array<Int> = arrayOf(1,2,3) var anyArray: Array<Any> = intArray // Exception occured. |
ただし、意図的に共変にすることもできる。
以下の通り、kotlinの配列は不変であるから Array
コンパイル時にひっかかる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
open class Hoge(var value:String) { open fun sayHello():Unit { println("hoge") } } class Fuga(value:String): Hoge(value) { final override fun sayHello(): Unit { println("fuga") } } var hogeArray: Array<Fuga> = arrayOf(Fuga("hoge"),Fuga("fuga")) //Error:(17, 34) Kotlin: Type mismatch: inferred type is Array<Fuga> but Array<Hoge> was expected var fugaArray: Array<Hoge> = hogeArray |
out予約語をつけることで、その配列を共変配列にできる!
1 2 3 4 5 6 7 8 9 10 11 12 |
open class Hoge(var value:String) { open fun sayHello():Unit { println("hoge") } } class Fuga(value:String): Hoge(value) { final override fun sayHello(): Unit { println("fuga") } } var hogeArray: Array<Fuga> = arrayOf(Fuga("hoge"),Fuga("fuga")) var fugaArray: Array<out Hoge> = hogeArray |
実際、コレクションのListはoutが指定してある。
なので、それを知らずともJava的に共変配列の操作のようなことができる。
1 2 3 4 |
interface List<out E>: Collection<E> var list: List<String> = listOf("aaa","bbb","ccc") var list2: List<Any> = list |