1. 型変性とは?
「型変性(variance)」とは、型パラメータの階層関係をどのように扱うかを示す性質です。
つまり、
DogがAnimalのサブタイプなら、
List<Dog>はList<Animal>のサブタイプになるのか? という話。
2. 基本の型階層(サンプル)
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
(簡単に言えば Dog ⊂ Animal)
3. 変性の3種類
| 変性タイプ | 意味 | Dartにおける扱い |
|---|---|---|
| 共変 (Covariant) | サブタイプが許可される | 戻り値・covariant修飾子 |
| 反変 (Contravariant) | スーパタイプが許可される | メソッド引数(デフォルト) |
| 非変 (Invariant) | どちらも許されない | ジェネリクス全般 (List<T>) |
4. 共変(Covariant)とは?
「サブタイプを代入してもOK」
Animal animal = Dog(); // ✅ OK(共変)
メソッドの戻り値は共変
class Trainer {
Animal train() => Animal();
}
class DogTrainer extends Trainer {
@override
Dog train() => Dog(); // ✅ OK(共変)
}
つまり、サブクラス型を返すのは安全です。
5. 反変(Contravariant)とは?
「スーパタイプを受け取れる(より広い型を受け入れる)」
メソッドの引数は反変
class Trainer {
void train(Animal a) {}
}
class DogTrainer extends Trainer {
@override
void train(Dog d) {} // ❌ NG!引数を狭めてはダメ
}
なぜ?
- 親クラスの変数で
Trainer trainer = DogTrainer();としたとき、 -
trainer.train(Animal())を呼べてしまう - でも
DogTrainer側はDogしか受け入れられない → 実行時エラー
→ Dart は安全のため、引数を狭めることを禁止しています。
6. 非変(Invariant)とは?
「型が完全に一致しないと代入できない」
Dart の ジェネリクス(List, Map, Setなど) はすべて非変です。
List<Dog> dogs = [Dog()];
// List<Animal> animals = dogs; // ❌ NG!
List<Animal> animals = dogs.cast<Animal>(); // ✅ 明示キャスト
なぜ非変?
- 型安全のため
- もし
animals.add(Cat())できたら、dogsにCatが混ざってしまう!
7. covariant 修飾子がもたらす例外
covariant を使うと、引数を共変にできます(=反変ルールを上書き)。
class Trainer {
void train(covariant Animal a) {}
}
class DogTrainer extends Trainer {
@override
void train(Dog d) {} // ✅ OK(covariantによる共変化)
}
でも注意
Trainer trainer = DogTrainer();
trainer.train(Animal()); // ❗ 実行時エラー!
→ コンパイルは通るが、実行時に失敗。
covariant は型安全を弱める代償つきの柔軟性です。
8. Dart の変性ルールまとめ(表)
| 要素 | デフォルト変性 | 共変許可 | 反変許可 | 使用例 |
|---|---|---|---|---|
| 戻り値 | 共変 | ✅ | ❌ |
Dogを返すoverride |
| 引数 | 反変 | ❌(通常) | ✅ |
Animalを引数に取る |
| ジェネリクス | 非変 | ❌ | ❌ | List<T> |
covariant引数 |
共変 | ✅ | ❌ | FlutterのElement.update()
|
Dartの安全思想:
「ジェネリクスの変性は非変」
「関数の戻り値は共変」
「引数は反変」
「covariantを明示すると例外を作れる」
9. Flutter内部での具体例:Element.update()
abstract class Element {
void update(covariant Widget newWidget);
}
class StatelessElement extends Element {
@override
void update(StatelessWidget newWidget) {
// 特定Widgetに限定できる!
}
}
Flutterはこの仕組みで、
StatelessElement が StatelessWidget に限定できるようにしている。
まとめ
| 項目 | キーワード | Dartでの扱い | メモ |
|---|---|---|---|
| 共変 | covariant | 戻り値/covariant修飾子 |
サブタイプ代入可 |
| 反変 | contravariant | メソッド引数(デフォルト) | スーパタイプ代入可 |
| 非変 | invariant | ジェネリクス | 一致しないと不可 |
| covariant修飾子 | ― | 引数型を共変化 | 実行時エラーリスクあり |
Dart の「型変性」は、安全性と柔軟性のバランス設計で
「共変=返す方向」「反変=受け取る方向」「非変=固定」と覚えると直感的です。