kotlin基本構文-ジェネリッック型,ジェネリック関数,共変と反変,out予約語

ジェネリック

プライマリコンストラクタで受け取った文字列をプロパティとして持つオブジェクトを作成し、
その文字列を取得するコードは以下の通り。普通。


class MyObject(var value: String) {
    fun getProp(): String {
        return this.value
    }
}
val m = MyObject("hogehoge")
println(m.getProp())

この書き方だと、型が文字列に限定される。
型を一般化して、文法で型と紐づける書式がジェネリック。
上記のコードをジェネリックで一般化してみる。
一般化した型引数をTとしている。


class MyGenerics(var value: T) {
    fun getProp(): T {
        return this.value
    }
}
val g1 = MyGenerics("hogehoge")
println(g1.getProp()) // hogehoge
val g2 = MyGenerics(100)
println(g2.getProp()) // 100

型推論による型指定の省略

プライマリコンストラクタに渡すパラメタの型からジェネリック型が唯一決まる場合は、
インスタンス化時の型指定を省略できる。


class MyGenerics(var value: T) {
    fun getProp(): T {
        return this.value
    }
}
val g1 = MyGenerics("hogehoge")
println(g1.getProp()) // hogehoge
val g2 = MyGenerics("fugafuga")
println(g2.getProp()) // fugafuga

複数のジェネリック型指定

複数のジェネリック型を指定できる。下記のような書き方になる。


class MyGenerics2(var value1: T, var value2: R) {
    fun getProp1(): T {
        return this.value1
    }
    fun getProp2(): R {
        return this.value2
    }
}
val g3 = MyGenerics2(100,"hoge")
println(g3.getProp1())
println(g3.getProp2())

型引数の制約

型引数TをT:typeとすると、type型かtype型の派生型という意味になり、型の制約を作れる。
ジェネリック型をHogeクラスの派生に制限する定義として、インスタンス化時にInt型を指定する例。
コンパイル時(前)にちゃんとエラーが出る。


class Hoge(var def: String)
{
    fun getVal(): String{
        return "hoge"
    }
}
class MyGenerics3(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(100)
println(g4.getProp())
val hoge = Hoge("hoge")
var g5 = MyGenerics3(hoge)
println(g5.getProp().getVal())

ジェネリック関数

関数の引数と戻り値を関数の呼び出し時に決めて紐づけられる仕組み。
ジェネリック型の定義はclassだが、ジェネリック関数はclassではなく関数。

以下は、リストの末尾(tail)を応答するジェネリック関数。
引数として配列を取るが、配列の型を呼び出し時に決められる。


fun  tail(list: Array): 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のインスタンスを入れた瞬間に怒られる。


Integer[] iary = new Integer[10];
Number[] aary = iary;
aary[0] = new Long(0); // Exception occured.

kotolinの配列は共変ではなく不変。
配列の要素に継承関係があったとしても代入できない。
これを不変(invariant)と言ってkotolinは不変が基本。


var intArray: Array = arrayOf(1,2,3)
var anyArray: Array = intArray  // Exception occured.

ただし、意図的に共変にすることもできる。
以下の通り、kotlinの配列は不変であるから ArrayをArrayに代入できない。
コンパイル時にひっかかる。


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 = arrayOf(Fuga("hoge"),Fuga("fuga"))
//Error:(17, 34) Kotlin: Type mismatch: inferred type is Array but Array was expected
var fugaArray: Array = hogeArray

out予約語をつけることで、その配列を共変配列にできる!


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 = arrayOf(Fuga("hoge"),Fuga("fuga"))
var fugaArray: Array = hogeArray

実際、コレクションのListはoutが指定してある。
なので、それを知らずともJava的に共変配列の操作のようなことができる。


interface List: Collection

var list: List = listOf("aaa","bbb","ccc")
var list2: List = list