ジェネリクス
基本的な配列型であるList
のAPIドキュメントを見ると、実際には型がList<E>
であることがわかります。<...>表記は、Listを*ジェネリック*(または*パラメータ化された*)型、つまり正式な型パラメータを持つ型としてマークします。慣例により、ほとんどの型変数には、E、T、S、K、Vなどの単一文字の名前が付けられています。
ジェネリクスを使う理由
#ジェネリクスは、型安全のためにしばしば必要となりますが、コードを実行できるようにするだけでなく、他にも利点があります。
- ジェネリック型を適切に指定することで、より良い生成コードが得られます。
- ジェネリクスを使用して、コードの重複を減らすことができます。
リストに文字列のみを含める場合は、List<String>
(「文字列のリスト」と読みます)として宣言できます。そうすることで、あなた、仲間のプログラマー、そしてあなたのツールは、リストに文字列以外のものを代入することがおそらく間違いであることを検出できます。次に例を示します。
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
ジェネリクスを使用するもう1つの理由は、コードの重複を減らすことです。ジェネリクスを使用すると、静的解析を利用しながら、多くの型間で単一のインターフェースと実装を共有できます。たとえば、オブジェクトをキャッシュするためのインターフェースを作成するとします。
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
このインターフェースの文字列固有バージョンが必要であることがわかったので、別のインターフェースを作成します。
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
後で、このインターフェースの数値固有バージョンが必要になることに気付きます... わかるでしょう。
ジェネリック型を使用すると、これらすべてのインターフェースを作成する手間を省くことができます。代わりに、型パラメータを取る単一のインターフェースを作成できます。
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
このコードでは、Tは代替型です。これは、開発者が後で定義する型と考えることができるプレースホルダーです。
コレクションリテラルの使用
#リスト、セット、マップのリテラルはパラメータ化できます。パラメータ化されたリテラルは、既に見てきたリテラルと変わりませんが、開始ブラケットの前に<型>
(リストとセットの場合)または<キー型、値型>
(マップの場合)を追加する点が異なります。型付きリテラルの使用例を次に示します。
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
コンストラクタでパラメータ化された型の使用
#コンストラクタを使用するときに1つ以上の型を指定するには、クラス名の直後に山かっこ(<...>
)で囲んだ型を配置します。次に例を示します。
var nameSet = Set<String>.from(names);
次のコードは、整数キーとView型の値を持つマップを作成します。
var views = Map<int, View>();
ジェネリックコレクションとそこに含まれる型
#Dartのジェネリック型は*具象化*されます。つまり、実行時に型情報が保持されます。たとえば、コレクションの型をテストできます。
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
パラメータ化された型の制限
#ジェネリック型を実装する場合、引数として指定できる型を制限して、引数が特定の型のサブタイプでなければならないようにすることができます。これは、extends
を使用して行うことができます。
一般的なユースケースは、型をObject
(デフォルトのObject?
ではなく)のサブタイプにすることで、型がnull許容値でないことを保証することです。
class Foo<T extends Object> {
// Any type provided to Foo for T must be non-nullable.
}
Object
以外の型でもextends
を使用できます。SomeBaseClass
を拡張する例を次に示します。これにより、SomeBaseClass
のメンバーを型Tのオブジェクトで呼び出すことができます。
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
ジェネリック引数としてSomeBaseClass
またはそのサブタイプを使用しても問題ありません。
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
ジェネリック引数を指定しないことも問題ありません。
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
SomeBaseClass
以外の型を指定するとエラーになります。
var foo = Foo<Object>();
ジェネリックメソッドの使用
#メソッドと関数も型引数を許可します。
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
ここで、first
のジェネリック型パラメータ(<T>
)を使用すると、型引数T
をいくつかの場所で se使用できます。
- 関数の戻り値の型(
T
) - 引数の型(
List<T>
) - ローカル変数の型(
T tmp
)
特に明記されていない限り、このサイトのドキュメントはDart 3.5.3を反映しています。ページの最終更新日:2024年4月8日。 ソースを表示 または 問題を報告する.