Dartのジェネリクスは共変
タイトルでオチがついている。Dartのジェネリクスって共変なんだねえ。
The Dart type system | Dart
https://dart.dev/guides/language/type-system#generic-type-assignment
共変って何?
あるクラスTsuper
がサブクラスTsub
をもつ場合に、
-
Tsuper
による総称型にTsub
による総称型を代入できることを共変といいます- Dartはこれに該当するということです。
- 対して、
Tsuper
型の変数にTsub
型の変数を代入できないことを不変といいます- Javaがこれに該当します。
具体的なコードによる説明
ジェネリクス型に互換性があり、代入ができるということを共変といいます
dartは互換性があるので共変です
List<num> numList;
List<double> doubleList = [];
numList = doubleList; // 代入ができる。これが共変
一見この互換性は便利そうですが、以下のようなリスクがあります。
List<num> numList;
List<double> doubleList = <double>[]; // [];の丁寧な書き方
numList = doubleList; // 代入ができる
// コンパイルこそ通るものの、
// doubleのリストにintをaddすることによって実行時エラーが発生する。
// 変数にnumListって書いてあるから動きそうなのにね。
numList.add(2 as int); // 実行時エラー!
ちなみにJavaはジェネリクス型に互換性がありません(不変)1
List<Number> numberList;
List<Double> doubleList;
// numberList = doubleList; // コンパイルエラー
Flutterにおける身近な共変の利用
例えばこういう画面クラス。
createState()
について、MyState
の型はState<MyPage>
であるにもかかわらず、State<StatefulWidget>
という戻り値として成立していますね。
State<StatefulWidget>
∋ State<MyPage>
であり、State<MyPage>
を State<StatefulWidget>
として扱ってよいことになっているので、この書き方が許容されます。
class MyPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyState();
}
}
class MyState extends State<MyPage> {
@override
Widget build(BuildContext context) {
return Scaffold(body: Text("this is view"));
}
}
MyPage.createState()の中身を冗長に書くとこういう風になります。
class MyPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
State<StatefulWidget> returnValue;
State<MyPage> myState = MyState();
returnValue = myState; // 共変なので代入ができる
return returnValue;
}
}
class MyState extends State<MyPage> {
// (略)
}
-
この制約を緩めて使いやすくするために、型の設計者はextendsとかsuperとかワイルドカードとかの署名をこねくり回します。 ↩