0
1

More than 3 years have passed since last update.

variance in kotlin

Posted at

variance in kotlin

varianceとは

パラメータ化されたクラス間の関係を表す
Variance describes the relationship between parameterized classes
Idiomatic Kotlin: Variance

パラメータ化されたクラスとは、List<MyClass>におけるMyClass

covariant, contravariant and invariant

共変 (covariant): 広い型(例:double)から狭い型(例:float)へ変換すること。
反変 (contravariant) : 狭い型(例:float)から広い型(例:double)へ変換すること。
不変 (invariant): 型を変換できないこと。
共変性と反変性 (計算機科学)

kotlinでは、デフォルトだと、
パラメータ化されたクラス間の関係は不変(型を変換できない)となる。

例えば、
List<String>を例にとると、
kotlinでは、StringAnyの派生型であるが
List<String>は、List<Any>へ変換できない。

kotlinにおける共変と反変

kotlinでは、outinキーワードを使って、共変と反変を表す。
outinを使わない場合は、不変となる。

List<String>を、List<Any>へ代入しようとすると、
以下の通り、type mismatchによりcomplie errorが発生する。

val ls = mutableListOf("String")
val la: MutableList<Any> = ls // Type mismatch

// 上のコードのコンパイルが通ると、
// 下記の通り、int型の変数を、MutableList<String>に代入できることになる
// ∵ MutableList<String>はMutableList<Any>に代入できるため
ls.add(42)

MutableList<Any>Stringを追加

fun <T> addAll(list1: MutableList<T>,
               list2: MutableList<T>) {
  for (elem in list2) list1.add(elem)
}

// MutableList<String>
val ls = mutableListOf("A String")
// MutableList<Any>
val la: MutableList<Any> = mutableListOf()

/**
* Type inference failed: 
* Cannot infer type parameter T in 
* fun <T> addAll(list1: MutableList<T>,list2: MutableList<T>)
*/
addAll(la, ls)  // compile error

Tが推量できないためエラーとなっている。

None of the following substitutions
(MutableList<Any>,MutableList<Any>)
(MutableList<String>,MutableList<String>)
can be applied to
(MutableList<Any>,MutableList<String>)

上のエラーメッセージのように、
引数で渡しているAnyStringに型Tが合致していない。
これは、型Tが不変であるからに他ならない。

outを用いて型を共変化

fun <T> addAll(list1: MutableList<T>,
               list2: MutableList<out T>) {
    for (e in list2) list1.add(e)
}

// MutableList<String>
val ls = mutableListOf("String")
// MutableList<Any>
val la: MutableList<Any> = mutableListOf()
val f = addAll(la, ls)

上記のコードはcompile errorが発生しない。
list2のMutableListの型Toutを付与したことで、
list2が、型T(ここではAny)において共変(AnyからStringへ変換)となる。

outが使用できる条件

なぜoutを付与することで、共変となるのか。

Covariance is read-only and useful as a return type (out)
共変は、読み取り専用で、戻り値の型として有効
Idiomatic Kotlin: Variance

MutableList<E>の定義を見てみる。

/**
 * A generic ordered collection of elements that supports adding and removing elements.
 * @param E the type of elements contained in the list. The mutable list is invariant on its element type.
 */
public interface MutableList<E> : List<E>, MutableCollection<E> {
    override fun add(element: E): Boolean
    override fun remove(element: E): Boolean
    override fun addAll(elements: Collection<E>): Boolean
    public fun addAll(index: Int, elements: Collection<E>): Boolean
    override fun removeAll(elements: Collection<E>): Boolean
    override fun retainAll(elements: Collection<E>): Boolean
    override fun clear(): Unit
    public operator fun set(index: Int, element: E): E
    public fun add(index: Int, element: E): Unit
    public fun removeAt(index: Int): E
    override fun listIterator(): MutableListIterator<E>
    override fun listIterator(index: Int): MutableListIterator<E>
    override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}

上記のコメントにあるように、型Eは、リストに含まれる要素の型であり、
mutable listはその型に関して不変(型を変換できない)である。

先ほどのaddAllで使用しているlist2の関数inは、
override fun iterator(): MutableIterator<E>で、
戻り値に型Eが指定されている。

これにより、outを付与することで、list2の型Eに対して共変が成立する。

つまり、addAll内で使用しているlist2の関数は、戻り値にのみEを指定しているため、
AnyからStringへ変換し、それを戻り値として返すことに矛盾が生じない。

一方で、list1の関数addは、
override fun add(element: E): Booleanで、
引数に型Eが指定されている。

そのため、list1outを付与することはできない。

fun <T> addAll(list1: MutableList<out T>, /* MutableList<String> */
               list2: MutableList<T> /* MutableList<Any> */) {
    for (e in list2) list1.add(e)
}


val ls = mutableListOf("String")
val la: MutableList<Any> = mutableListOf()
val f = addAll(ls, la)

ここで共変を許容すると、
AnyからStringへ変換した要素EMutableList<Any>に追加できることになる。

inoutの逆

Contravariance is write-only and useful as an input type (in)
反変は、書き込み専用で、引数の型として有効
Idiomatic Kotlin: Variance

先ほどの例でいうと、list1inを指定することでコンパイルが通る。

fun <T> addAll(list1: MutableList<in T>, /* MutableList<String> */
               list2: MutableList<T> /* MutableList<Any> */) {
    for (e in list2) list1.add(e)
}


val ls = mutableListOf("String")
val la: MutableList<Any> = mutableListOf()
val f = addAll(ls, la)

list1の関数addは、引数にEが指定されているため、
StringからAnyへ変換し、MutableListに追加することに矛盾は生じない。

これにより、反変が成り立つ

Reference

The Joy of Kotlin

0
1
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
0
1