🧵
【Dart】安全で効率的なコードを書くためのGenerics(総称型)基礎
【Dart】Generics(総称型)
はじめに
-
【参考】公式サイト
-
Generics(総称型)とは
- 利用目的
- 特定の型を記載せず総称型を仮置きし、後で決定する
- メリット
- 重複したコードを無くせる
- 違う型の変数に代入すると、コンパイルエラーで教えてくれる
- 利用目的
-
実行環境
-
DartPadやAndroid Studio等で実行
- Dart SDK 2.10.4
-
DartPadやAndroid Studio等で実行
Genericsを使わない場合
- String変数を持つUserクラス
- イメージ:お店や病院の呼び出し名が名前(文字列)
class User {
final String callName;
User(this.callName);
}
void main() {
final user = User('Tanaka');
print(user.callName);
}
実行結果
Tanaka
- int変数を持つUserクラス
- 上述のコードが先に存在する場合は、クラス変更や、名前を変えて複数作成する必要がある
- イメージ:お店や病院の呼び出し名が番号(数字)
class User {
final int callName;
User(this.callName);
}
void main() {
final user = User(1);
print(user.callName);
}
実行結果
1
- dynamic変数を持つUserクラス
-
dynamic callName
とすると、文字列を持てる- ※
final callName
と記載すると、callNameはdynamic型となる
- ※
-
class User {
final callName;
User(this.callName);
}
void main() {
final user = User('Tanaka');
print(user.callName);
}
実行結果
Tanaka
- dynamic変数を持つUserクラス
-
dynamic callName
とすると、同様に数字も持てる
-
class User {
final callName;
User(this.callName);
}
void main() {
final user = User(1);
print(user.callName);
}
実行結果
1
- dynamic変数は便利だが、実行時エラーでしか気づけないバグを作ってしまう可能性がある
- エラーとなる例
-
dynamic callName
だと、intで値を作成した後に、Stringの変数に代入しようとするとコンパイルエラーとならずに、実行時エラーとなる - エラーとしないために以下等が可能だが、忘れると実行時エラーとなるため危険
String name = user.callName.toString();
-
- エラーとなる例
class User {
final callName;
User(this.callName);
}
void main() {
final user = User(1);
print(user.callName);
String name = user.callName;
}
実行結果
1
Uncaught Error: TypeError: 1: type 'JSInt' is not a subtype of type 'String'
Genericsを使う場合
- Genericsを持つUserクラス
- コンパイルエラーとなる(IDEが教えてくれる)
-
String name = user.callName;
で以下エラーA value of type 'int' can't be assigned to a variable of type 'String'
-
- コンパイルエラーとなる(IDEが教えてくれる)
class User<T> {
final T callName;
User(this.callName);
}
void main() {
final user = User(1);
print(user.callName);
String name = user.callName;
}
実行結果
Error compiling to JavaScript:
main.dart:9:22:
Error: A value of type 'int' can't be assigned to a variable of type 'String'.
String name = user.callName;
^
Error: Compilation failed.
- Genericsを持つUserクラス
-
T callName
とすると、文字列を持てる
-
class User<T> {
final T callName;
User(this.callName);
}
void main() {
final user = User('Tanaka');
print(user.callName);
}
実行結果
Tanaka
- Genericsを持つUserクラス
-
T callName
とすると、同様に数字も持てる - クラスを書き換えることなく、安全に複数の型が利用可能
-
class User<T> {
final T callName;
User(this.callName);
}
void main() {
final user = User(1);
print(user.callName);
}
実行結果
1
- 【備考】
<T>
は<TEST>
等何でもよい- 慣習的に主に
T
を利用
- 慣習的に主に
class User<Test> {
final Test callName;
User(this.callName);
}
void main() {
final user = User('Tanaka');
print(user.callName);
}
実行結果
Tanaka
- 明示的に型を記載
-
User<String>('Tanaka')
やUser<int>(1)
とすることで、Genericを利用していることが分かりやすくなる
-
class User<T> {
final T callName;
User(this.callName);
}
void main() {
final user = User<String>('Tanaka');
print(user.callName);
}
実行結果
Tanaka
Genericsの階層
- Genericsを持つクラスを継承
- 注意
- スーパークラスのGenericsの名前と合わせる
-
class SingleUser<A> extends User<T> {}
だと以下エラーThe name 'T' isn't a type so it can't be used as a type argument
-
- スーパークラスの指定では、後ろの
<T>
まで必要-
extends User<T>
ではなく、extends User
とすると、user1.callName
がdynamic型になってしまう
=違う型の変数に代入しても、コンパイルエラーで教えてくれない
-
- スーパークラスのGenericsの名前と合わせる
- 注意
abstract class User<T> {
final T callName;
User(this.callName);
void call();
}
class SingleUser<T> extends User<T> {
SingleUser(T callName) : super(callName);
void call() {
print('call SingleUser');
print(callName);
}
}
class MultiUser<T, K> extends User<T> {
final K callNamePlus;
MultiUser(T callName, this.callNamePlus) : super(callName);
void call() {
print('call MultiUser');
print(callName);
print(callNamePlus);
}
}
void main() {
final user1 = SingleUser<String>('Tanaka');
user1.call();
final user2 = MultiUser<int, String>(1, 'Tanaka');
user2.call();
}
実行結果
call SingleUser
Tanaka
call MultiUser
1
Tanaka
- Genericsを持つクラスを継承する際に、型を制限する場合
-
User<T extends num>
とし、数字(int or double)指定とする- intとdoubleはnumのサブクラス(詳細は以下)
- 文字列制限とする場合は
T extends String
とする
-
abstract class User<T extends num> {
final T callName;
User(this.callName);
void call();
}
class SingleUser<T extends num> extends User<T> {
SingleUser(T callName) : super(callName);
void call() {
print('call SingleUser');
print(callName);
}
}
class MultiUser<T extends num, K> extends User<T> {
final K callNamePlus;
MultiUser(T callName, this.callNamePlus) : super(callName);
void call() {
print('call MultiUser');
print(callName);
print(callNamePlus);
}
}
void main() {
final user1 = SingleUser<int>(1);
user1.call();
final user2 = MultiUser<int, String>(2, 'Tanaka');
user2.call();
}
実行結果
call SingleUser
1
call MultiUser
2
Tanaka
Generics関数
- T(TEST等でもよいが、慣習的にTやK)を戻り値とした書き方
- Tを引数とし、Tを返す関数
T myself<T>(T name) => name;
- Tを引数とし、何か処理
void check<T>(T val) {}
- 備考
- Genericsを利用したクラスでなくとも実装可能
- 詳細例:sdk/GENERIC_METHODS.md at master · dart-lang/sdk
- Tを引数とし、Tを返す関数
abstract class User<T extends num> {
final T callName;
User(this.callName);
// Generics methods
T myself<T>(T name) => name;
void check<T>(T val) {
print('型名は以下');
print(T);
print(val);
}
}
class MultiUser<T extends num, K> extends User<T> {
final K callNamePlus;
MultiUser(T callName, this.callNamePlus) : super(callName);
}
void main() {
final user = MultiUser<int, String>(2, 'Tanaka');
final int userNameInt = user.myself<int>(user.callName);
final String userNameString = user.myself<String>(user.callNamePlus);
print(userNameInt);
print(userNameString);
user.check<int>(user.callName);
user.check<String>(user.callNamePlus);
}
実行結果
2
Tanaka
型名は以下
int
2
型名は以下
String
Tanaka
Discussion