共変性と反変性、JavaとKotlinの場合

# 共変性と反変性とはなんぞや？
共変性と反変性について、うろ覚えだったので改めて再確認し、忘れないようにここに書いておきます。
「共変性」と「反変性」言葉としてはあまり日常的にはでてきませんが、無意識のうちにつかっています。

理解できているようでなかなか理解ができていない、説明するのも難しいです。

[「共変性」と「反変性」とは、Wikipediaから](https://ja.wikipedia.org/wiki/%E5%85%B1%E5%A4%89%E6%80%A7%E3%81%A8%E5%8F%8D%E5%A4%89%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6))

> （コンピュータプログラミングの型システムでの）共変性と反変性（きょうへんせいとはんぺんせい、covariance and contravariance）とは、データコンテナのサブタイプ関係が、そのデータ要素のサブタイプ関係に連動して定義されるという概念を指す。また、関数の型のサブタイプ関係での、引数型と返り値型の汎化特化の制約を定義する概念でもある。ジェネリックなデータ構造、関数の型、クラスのメソッド、ジェネリックなクラス、ジェネリック関数などに適用されている。

# Javaの場合、まず、非ジェネリック型の場合
なんのことだかさっぱりこれだけではわかりませんね。とりあえず、Javaの場合。とりあえず、このようなクラスがあると仮定します。動物は抽象的なクラス(親クラス、superクラス)。犬は動物を具象化したクラス（子クラス、subクラス）。になります。
```java
/** 動物 */
class Animal {
}
/** 犬 */
class Dog extends Animal {
}
/** 猫 */
class Cat extends Animal {
}
```
これをお互いに代入しようとした場合、
```java
// ※1
Animal animal = new Dog();

// ※2
Dog dog = new Animal(); // コンパイルエラー
Dog dog = (Dog)new Animal(); // キャストすれば代入可能だが、ClassCastExceptionの可能性が出てくる。
```
※1はできますが、※2はコンパイルエラーになります。Animalをinterfaceで書いても※1はできます。※2は文法的に書けません。
```java
interface Animal {
    public void cry();
}

class Dog implements Animal{
    @Override
    public void cry() {
        System.out.println("Wooon");
    }
}

Animal animal = new Dog();
```
Javaは、代入においてこのようなルールがあります。kotlinにおいても同じです。
* __代入する値がよりデータ幅の広い型(親クラス、superクラス)の変数に代入する場合はJavaが自動的にキャストを行う。__
* __代入する値がよりデータ幅の狭い型(子クラス、subクラス)の変数に代入する場合はエラーになるため、プログラマによるキャストが必要__

# Javaの場合、ジェネリック型の場合
ジェネリック(generic)型の場合はどうなるでしょうか？ジェネリック型はJavaではJava5で導入されています。
```java
// ※1
List<Animal> animalList = new ArrayList<Dog>(); // コンパイルエラー

// ※2
List<Dog> dogList = new ArrayList<Animal>(); // コンパイルエラー
```
非ジェネリック型でできた※1もコンパイルエラーになり、※1も※2も両方コンパイルエラーになります。ジェネリック型の場合は非ジェネリック型と違って、ジェネリックの型が一致していないと代入できません。
これを不変 (invariant)と言います。
```java
// ※1
List<Animal> animalList = new ArrayList<Animal>();

// ※2
List<Dog> dogList = new ArrayList<Dog>();
```
# そこで、「共変性」と「反変性」の話が出てくる
ここで、ようやく「共変性」と「反変性」の話がでてくるのですが、
* __共変 (covariant): 広い型（親クラス、superクラス）から狭い型（子クラス、subクラス）へ変換すること。__
* __反変 (contravariant) : 狭い型（子クラス、subクラス）から広い型（親クラス、superクラス）へ変換すること。__

## Javaの場合の「共変性」と「反変性」の表現
Javaの場合は、< >の中のジェネリック型の表記にextends、superを使います。
```java
// ※1
List<? extends Animal> animalList = new ArrayList<Dog>();

// ※2
List<? super Dog> dogList = new ArrayList<Animal>();
```
※1は共変。左辺の代入される側が広い型(Animal)から狭い型(Dog)に変換されています。Animal → Dogに型変換だからextends。これはしっくりきます。
※2は反変。逆に左辺の代入される側が狭い型(Dog)から広い型(Animal)に変換されています。Dog → Animalに型変換だからsuper。これはしっくりきます。
## Kotlinの場合の「共変性」と「反変性」の表現
これがkotlinだとどうなるでしょうか？kotlinの場合はこれらをin、outを使って表現するのだが、Javaと違ってややこしい。理解としてあっているかどうか、はなはだ怪しいが、上記のJavaの場合と同じことやろうとするとこうなる。

### 共変(covariant)の場合
```kotlin
fun hoge() {
    val animalList: MutableList<out Animal> = mutableListOf<Dog>()
}
```
上はコレクションフレームワークの例だが、独自クラスの場合、コストラクタの引数のプロパティは変更できない。読み取りのみになる。
```kotlin
class Biology<out T: Animal>(_content: T) {
    val content = _content // varにできない
}
```
または
```kotlin
class Biology<out T: Animal>(val content: T) { // varにできない
}
```
varに変更しようとするとコンパイルエラーになる。
![Screenshot_20230312_172108.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/2b0f7a1d-2736-3471-e302-f7081bcbf042.png)
```kotlin
    fun test1() {
        var animal = Biology(Animal())
        var dog = Biology(Dog())
        animal = dog // これはok
    }
    fun test2() {
        var animal = Biology(Animal())
        var dog = Biology(Dog())
        dog = animal // コンパイルエラー
    }
```

### 反変(contravariant)の場合
```kotlin
fun hoge() {
    val dogList: MutableList<in Dog> = mutableListOf<Animal>()
}
```
独自クラスの場合、コンストラクタの引数のプロパティはprivateでなければならない。外からの参照は許されない。
```kotlin
class Biology<in T: Animal>(_content: T) {
    private var content = _content // privateでなければいけない
}
```
または
```kotlin
class Biology<in T: Animal>(private var content: T) { // privateでなければいけない
}
```
publicにしようとするとコンパイルエラーになる
![Screenshot_20230312_172651.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/9b7676cb-3362-3125-e8b2-daf13b551005.png)
```kotlin
fun test1() {
    var animal = Biology(Animal())
    var dog = Biology(Dog())
    dog = animal // これはok
}
fun test2() {
    var animal = Biology(Animal())
    var dog = Biology(Dog())
    animal = dog // コンパイルエラー
}
```
# まとめ
Javaの共変=extend、反変=superは名前的にもしっくりくるが、kotlinのout、inは名前の付け方がイマイチしっくりこない。(何で、inなのか？何で、outなのか？)
kotlinは内部的にはJavaのソースコードに変換され、同じようにJVM上で動作するので、振る舞い的にはJavaもKotlinも同じであろうと思う。kotlinにはkotlinの何かポリシーがあったんだろうか・・・？

と、言うわけで、JavaとKotlinの共変、反変について簡単にまとめると以下のようになるが、色々なサイトで共変、反変の説明を見ても、これだという決定打がなく。イマイチ自分でも煮えきらないので、何か補足があればコメントよろしくおねがいします。

|  | |java |kotlin|プロパティ|
|:-:|:-|:-|:-|:-|
| 共変(covariant)|広い型から狭い型へ変換|extends | out |変更不可(val)|
| 反変(contravariant)|狭い型から広い型へ変換|super | in |外から参照不可(private)|

ps.コメント頂きました

* 共変(covariant)の場合は、上記の例の通り、コンストラクタの引数がvalで参照のみとなります。参照のみなので、出力のみ、なのでout。
* 反変(contravariant)の場合は、上記の例の通り、コンストラクタの引数はprivate varとなります。変更のみなので、に入力のみなのでin。

ちょっと、覚えづらいですね。

zennにいい記事がありました。
[筒で理解する反変・共変](https://zenn.dev/igrep/articles/2024-10-1-variance)
[見て理解するジェネリクス 基礎から共変・反変までバッチリ習得](https://zenn.dev/suzuki_hoge/books/2024-12-generics-7649c6a0a47667)