前のページ

言語編

次のページ


総称型 (Generics)

基本的な配列の型であるListAPIドキュメンテーションを見ると、そこにはその型は実際はList<E>であることが判る。<…>記述はListは総称(或いはパラメタ化された)型、即ち正式の型パラメタたちを持っている型であることを示す。総称型の殆どの型パラメタは慣行的に E, T, S, K, 及び Vといった1文字の名前を持っている。



どうして総称型を使うのか (Why use generics?)

総称型はしばしば型安全性が求められるものの、単に自分のコードを走れるようにするということ以上の利点がある:

  • 適切な総称型を指定することで生成されたコードがより良いものとなる

  • 総称型を使うと重複したコードを減らすことができる

もしあるリストを文字列のみを含むようにしたいときは、List<String>(”文字列のリスト (list of string)”と読む)を宣言できる。そのほうが自分、同僚、そして自分のツールがそのリストに文字列でないものを代入しているのは多分ミスだと検出できる。以下はその例である:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // エラー

総称型を使うもう一つの理由はコードの重複を減らすことである。総称型により、多くの型間で単一のインターフェイスと実装を共有できるようになる一方で、静的解析の利点が維持される。例えば、あるオブジェクトを捕捉するインターフェイスを作りたいとする:

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 StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

このコードの中でTは代行型(stand-in type)である。これは取りあえずある型として一時的に置いておくもので、デベロッパが後でそれを定義すればよい。



collectionのリテラルを使う (Using collection literals)

ListMapのリテラルもパラメタ化できる。パラメタ化されたリテラルは、括弧を開く前に<type>(リストの時)または<keyType, valueType>(マップの時)を付加することを除いて既に見てきたリテラルと似ている。以下は型付けされたリテラルを使った例である:

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};



コンストラクタを持ったパラメタ化された型を使う (Using parameterized types with constructors)

コンストラクタを使う際はクラス名の直後に山形ブラケット(<...>)の中にその型を置く。例えば:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);

以下のコードは整数のキーとView型の値を持つマップを作っている:

var views = Map<int, View>();



総称型のコレクションとそれらが含む型 (Generic collections and the types they contain)

Dartの総称型はJavaと違って具体化された総称型(reified generics)に対応している。即ち総称型のオブジェクトたちはそれらの型引数たちの情報を実行時に保持する。総称型のコンストラクタに型引数を渡すのはランタイムの操作である。例えば、あるコレクションの型をテストできる:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意:これに比べJavaに於ける総称型は型削除(erasure)であり、これは総称型の型パラメタはランタイムで削除されることを意味する。Javaでは、あるオブジェクトがListかどうかをテストできるが、List<String>であるかはテストできない。



パラメタ化された型を制限する (Restricting the parameteried type)

ある総称型を実装しようとする際、そのパラメタたちの型を制限したいことがあろう。extendsを使うとそれは可能である。

class Foo<T extends SomeBaseClass> {
  // 実装はここに入る
  String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}

SomeBaseClassまたはそのサブクラスたちのどれかを総称引数として使うことはOKである:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

総称型の引数がないことを指定することもOKである:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

SomeBaseClass型を指定するとエラーとなる:

var foo = Foo<Object>();



総称メソッドを使う (Using generic methods)

当初Dartの総称型サポートはクラスたちに限られていた。総称型メソッド(generic methods)と呼ばれる新しい構文では、メソッドと関数での型引数を許している:

T first<T>(List<T> ts) {
  // ここで初期作業とかエラー・チェックをして、次に...
  T tmp = ts[0];
  // 追加のチェックまたは処理を行う...
  return tmp;
}

ここではfirst (<T>)上の総称型パラメタで、型引数Tを幾つかの場所で使えるようになる:

  • この関数の戻りの型(T)

  • 引数の型の中(List<T>)

  • ローカル変数の中(T tmp)

総称型に関する更なる情報はUsing Generic Methodsにある。





前のページ

次のページ