ジェネリック (generic) : 総称

C#などでは「ジェネリック」と表記されていますが、Javaの日本語ドキュメントではJava SE6まで「総称」、Java SE7では「ジェネリクス」となっています。

ジェネリックのおもな用途はコレクションです。

ジェネリック型宣言 (generic type declaration)

class クラス名<E> {
    E フィールド名;
}

このときクラス名原型 (raw type) と呼ばれます。型変数 (type variable)「E」は、型パラメータ (type parameter) と呼ばれ、オブジェクトの生成時には具体的な型で置き換えます。

型変数の名前に特別な意味はありませんが、一般的には次の名前が使用されます。

  • E … Element (Javaのコレクション フレームワークで多用されています)
  • K … Key
  • N … Number
  • T … Type
  • V … Value
  • S、U、Vなど … 2番、3番、4番目の型

複数の型パラメータ

型パラメータは、カンマ区切りで複数記述できます。

class クラス名<E1, E2, ..., En> {}

ジェネリック型の使用方法

オブジェクトの生成

たとえば、

class Foo<E> {
    E bar;
}

と定義されたジェネリック型のオブジェクトを生成するには、

Foo<Integer> foo = new Foo<Integer>();

のように記述します。

型変数<E>に指定できるのは参照型のみで、基本型は使えません。よって、たとえばList<int>とは書けず、ラッパークラスを使用してList<Integer>とする必要があります。もし基本型を指定するならば「Syntax error on token "int", Dimensions expected after this token」のようなエラーとなります。

ジェネリック型であることから、その型変数を型とするフィールドbarには、生成時に指定した型と同一の型を設定できます。

Foo<Integer> foo1 = new Foo<Integer>();
foo1.bar = 10; // 数値を設定

Foo<String> foo2 = new Foo<String>();
foo2.bar = "abc"; // 文字列を設定

なお、生成時に型を指定しないと「Foo is a raw type. References to generic type Foo<E> should be parameterized」のように警告されます。java - What is a raw type and why shouldn't we use it? - Stack Overflow

コンストラクタの型パラメータの省略

Java SE 7以降では、コンストラクタの型変数を省略できます。つまり、

Foo<Integer> foo = new Foo<Integer>();

の記述は、

Foo<Integer> foo = new Foo<>();

のように簡略化できます。ちなみにこのときの「<>」を、ダイヤモンド (diamond) と呼称します。

ジェネリック インターフェイスの実装

インターフェイスが、

interface Foo<E> {
    void Bar(E value);
}

のようにジェネリック型として定義されているとき、これをクラスで実装するには

class Abc implements Foo<Integer> {
    public void Bar(Integer value){}
}

のようにします。一方でジェネリックなクラスとして実装するには、

class Abc<E> implements Foo<E> {
    public void Bar(E value){}
}

とします。

ジェネリック クラスの継承

前項のジェネリック インターフェイスの実装と、考え方は同じです。

境界のある型パラメータ (bounded type parameters)

通常、型パラメータには任意の型を適用できます。しかし、ときには適用される型を制限したい場合もあるでしょう。そのような場合には、

class Foo<E extends Bar> {}

のように記述することで、ジェネリック型Fooのオブジェクトを生成するとき、その型に指定できるのは、クラスBarを継承したものに限定されるようになります。なお、このときの型パラメータ「E」を、境界のある型パラメータと呼びます。

多数の境界 (multiple bounds)

多数の型を適用対象にできます。それには、

class Foo<E extends A & B & C> {}

のように、複数の型を「&」で連結して記述します。このとき型の1つがクラスならば、その型は必ず最初に記述しなければなりません。またクラスの継承と同様に、拡張できるクラスは1つのみです。

境界ワイルドカード (bounded wildcard)

型引数のワイルドカードである「?」を使用することで、適用される型変数を拡張できます。

たとえば次のように定義されたメソッドには、

void Foo(List<Number> list) {}

引数としてList<Number>しか渡せません。これを、

void Foo(List<? extends Number> list) {}

とすることで、NumberもしくはNumberのサブクラスであるListを渡せるようになります。

境界の種類

拡張する範囲によって、境界ワイルドカードは次の3つがあります。

種類 適用範囲の変化 サンプルコード
上限境界ワイルドカード サブクラスに拡張
List<? extends Integer>
下限境界ワイルドカード スーパークラスに拡張
List<? super Integer>
境界ワイルドカード 任意の型に拡張
List<?>
List<? extends Object>と同義

参考

参考書

Javaのドキュメントから検索