共変 / 反変
| 対応: | Kotlin 1.0(2016) |
|---|
Kotlinのジェネリクスで『out』(共変)を付けると生産者として読み取り専用に、『in』(反変)を付けると消費者として書き込み専用になります。型の安全な上位互換・下位互換を実現します。『out』(共変)はデータを取り出す側、『in』(反変)はデータを受け取る側で使います。例えば List<out Animal> は Animal のリストとして読み出し専用になります。
構文
// 共変(out) — T の上位型として扱える(読み取り専用・Producer)
class Producer<out T>(val value: T) {
fun get(): T = value
}
// 反変(in) — T の下位型として扱える(書き込み専用・Consumer)
class Consumer<in T> {
fun consume(value: T) { println(value) }
}
『out』の場合、T を引数に取るメソッド(set など)は定義できません。『in』の場合、T を返すメソッド(get など)は定義できません。
構文一覧
| キーワード | 呼び方 | 概要 |
|---|---|---|
| out T | 共変(Covariant) | T の上位型に代入可。T は返り値のみに使えます(Producer)。 |
| in T | 反変(Contravariant) | T の下位型に代入可。T は引数のみに使えます(Consumer)。 |
| T(変位なし) | 不変(Invariant) | 上位・下位型への代入不可。読み書き両方で使えます。 |
| List<out T> | 使用箇所変位 | Kotlin 標準ライブラリでの共変の例です。 |
サンプルコード
sample_covariant_contravariant.kt
// 共変(out)の例 — Combatant の上位型として扱える
open class Combatant(val name: String)
class DojimaMember(name: String) : Combatant(name)
class MajimaMember(name: String) : Combatant(name)
class Squad<out T : Combatant>(val member: T)
// 反変(in)の例 — Combatant の下位型として扱える
interface Coach<in T : Combatant> {
fun train(combatant: T)
}
fun main() {
// 共変: Squad<DojimaMember> を Squad<Combatant> として扱える
val dojimaSquad: Squad<DojimaMember> = Squad(DojimaMember("桐生一馬"))
val squad: Squad<Combatant> = dojimaSquad // out があるので代入OK
println(squad.member.name) // 桐生一馬
// 反変: Coach<Combatant> を Coach<DojimaMember> として扱える
val generalCoach: Coach<Combatant> = object : Coach<Combatant> {
override fun train(combatant: Combatant) {
println("${combatant.name} と戦います。")
}
}
val dojimaCoach: Coach<DojimaMember> = generalCoach // in があるので代入OK
dojimaCoach.train(DojimaMember("真島吾朗")) // 真島吾朗 と戦います
// Kotlin 標準の List は out T(共変)
val dojima: List<DojimaMember> = listOf(DojimaMember("秋山駿"), DojimaMember("錦山彰"))
val all: List<Combatant> = dojima // OK
println(all.map { it.name }) // [秋山駿, 錦山彰]
}
covariant_contravariant.kt
kotlinc covariant_contravariant.kt -include-runtime -d covariant_contravariant.jar java -jar covariant_contravariant.jar 桐生一馬 真島吾朗 と戦います。 [秋山駿, 錦山彰]
概要
変位指定のないジェネリクス(不変)は最も制限が厳しく、『Box<Dog>』を『Box<Animal>』に代入できません。『out』を付けた共変クラスは型を読み取るだけの「生産者」、『in』を付けた反変クラスは型を消費するだけの「消費者」として機能します。
Kotlin の標準ライブラリでは『List<out E>』が共変の代表例です。読み取り専用リストなので上位型に代入できます。一方『MutableList<E>』は不変のため代入できません。
ジェネリクスの基本はジェネリクス — 基本を、実行時の型情報保持はreified 型パラメーターを参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。