9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlin の ジェネリクスと変性

Last updated at Posted at 2018-05-16

なぜ変性が必要か

  • 変性には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)
}
9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?