なぜ変性が必要か
- 変性には3種類ある。
- 不変(invariance) 、共変(covariance) と 反変(contravariance)
- 以下にそれぞれ説明する。
不変
fun main(args: Array<String>){
var fooStr: Foo<String> = object: Foo<String>{ //...(1)
override fun f(): String {
return "foo"
}
}
var fooAny: Foo<Any> = object: Foo<Any>{ //...(2)
override fun f(): Any {
return "foo"
}
}
fooAny = fooStr //...(3)
var anyVar: Any = fooAny.f() //...(4)
}
//Foo のメソッド は 型R を返すのみで、引数には使わない。
interface Foo<R>{
fun f(): R
}
- 上記のコードは (3) でコンパイルエラーとなる。
- しかし、仮にコンパイルが通ったとしたら、 (4) はどうなるか。
- (4) で問題が起きることはない。 Foo のメソッド f は R型を返すのみなので、 Any に String を格納することは問題ない。
- つまり、 Any のサブタイプが String であるように、 Foo<String> を Foo<Any> のサブタイプとみなしても矛盾は起こらない。
- Any が Stringの親 であるように、
- Foo<Any> は Foo<String>の親 とみなせる。
- にも関わらず、(3)でコンパイルエラーとなるのは変性が不変だからである。
- それを解決するのが共変となる。
- 以下に、共変を用いたコードを示す。
共変
fun main(args: Array<String>){
var fooStr: Foo<String> = object: Foo<String>{ //...(1)
override fun f(): String {
return "foo"
}
}
var fooAny: Foo<out Any> = object: Foo<Any>{ //...(2) Foo(out Any) に書き換えた。
override fun f(): Any {
return "foo"
}
}
fooAny = fooStr //...(3)
var anyVar: Any = fooAny.f() //...(4)
}
//Foo のメソッド は 型R を返すのみで、引数には使わない。
interface Foo<R>{
fun f(): R
}
- 上記のコードは、(2) を Foo<out Any> に書き換えた。
- これは、Any を戻り値のみに使用することを明記することで、 (3) の実行を可能にしている。
- これが共変である。
- 共変とは
- Any が Stringの親 であるように、
- Foo<Any> は Foo<String>の親 となる
- この親子関係が共に変わると考えるとわかりやすい。
反変
fun main(args: Array<String>){
var barStr: Bar<String> = object: Bar<String>{ //...(1)
override fun f(x: String){
println(x)
}
}
var barAny: Bar<Any> = object: Bar<Any{ //...(2)
override fun f(x: Any){
println(x)
}
}
barStr = barAny //...(3)
barStr.f("bar") //...(4)
}
//Bar のメソッド は 型 T を引数にとるが、戻り値には使用しない。
interface Bar<T>{
fun f(x: T)
}
- 上記のコードは (3) でコンパイルエラーとなる。
- しかし、仮にコンパイルが通ったとしたら、 (4)はどうなるか。
- (4) で問題が起きることはない。
- (3) の時点で、 barStr に Bar<Any> のインスタンスが入っているが、そのメソッドf はAny の引数を受け取ることができるので、 (4)で "bar" を受け取ることができる。
- この性質は、
- Any は String の親であるが、
- Bar<Any> は Bar<String> の子であると解釈できる。
- にも関わらず、コンパイルエラーとなるのは、上記のコードの変性が不変だからである。
- それを解決するのが、反変である。
- 以下に半変を用いたコードを示す。
fun main(args: Array<String>){
var barStr: Bar<in String> = object: Bar<String>{ //...(1)
override fun f(x: String){
println(x)
}
}
var barAny: Bar<Any> = object: Bar<Any{ //...(2)
override fun f(x: Any){
println(x)
}
}
barStr = barAny //...(3)
barStr.f("bar") //...(4)
}
//Bar のメソッド は 型 T を引数にとるが、戻り値には使用しない。
interface Bar<T>{
fun f(x: T)
}
- 上記のコードは (1) を Bar<in String> に書き換えた。
- これは、Barのメソッド内で String を引数のみに利用することを明記することで、(3)の実行を可能にしている。
- これによって、
- Any は String の親
- Bar<Any> を Bar<String> の子の様に扱うことができる。
- Bar の親子関係が、共変とは逆であり、これを反変と呼ぶ。
インタフェース定義時に変性を指定する
- これまでの説明では、main 関数内で変性のキーワード(in, out) を指定していたが、interface 内で書くと便利である。
- 以下の様に、変性のキーワードを interface内で指定すると良い。
fun main(args: Array<String>){
// 中略
}
//Foo のメソッド は 型R を返すのみで、引数には使わない。
interface Foo<out R>{
fun f(): R
}
//Bar のメソッド は 型 T を引数にとるが、戻り値には使用しない。
interface Bar<in T>{
fun f(x: T)
}