この章では、総称(ジェネリクス)を使い、主にコレクション的な使い方を例としてあげます。
非総称バージョンのクラス例
public class GenericsClass {
private Object t;
public void set(Object t)
{
this.t = t;
}
public Object get()
{
return this.t;
}
}
総称型
public class GenericsClass<T> {
private T t;
public void set(T t)
{
this.t = t;
}
public T get()
{
return this.t;
}
}
TはクラスGenericsClass仮型パラメータ(formal type parameter)とも呼ばれる。
宣言して使う時は下記の様なコードとなる。
GenericsClass<String> boxString = new GenericsClass<String>();
boxString.set("さくさん");
System.out.println(boxString.get());
GenericsClass<Integer> boxInteger = new GenericsClass<Integer>();
boxInteger.set(new Integer(393));
System.out.println(boxInteger.get());
boxStringに互換性の無いIntegerの型を入れようとすると、
GenericsClass<String> boxString = new GenericsClass<String>();
boxString.set(new Integer(393));
System.out.println(boxString.get());
コンパイル時に下記の様なエラーが出て、コンパイル時に問題がある事が判ります。
set(java.lang.String) (helloworld.GenericsClass<java.lang.String> 内) を (java.lang.Integer) に適用できません
boxString.set(new Integer(393));
- ジェネリクス型は複数の型パラメータを取る事が出来るが、一意である必要があります。
// これはコンパイルエラーが発生する
public class GenericsClass<T, T> {
}
// これはコンパイルエラーは発生しない
public class GenericsClass<T, U> {
}
型パラメータのしきたり
E Element
K Key
N Number
T Type
V Value
S,U,V 第2、第3、第4のType
総称メソッドと総称コンストラクタ
総称メソッドを使えば、下記の様なことが出来ます。
public class SuperClass {
public <T> void nakigoeExt(T t)
{
System.out.println(t.getClass().getName() + " だわん!");
}
}
public static void main(String[] args) {
SuperClass superCls = new SuperClass();
superCls.nakigoeExt("さくさん"); // java.lang.String だわん!
superCls.nakigoeExt(new Integer(393)); // java.lang.Integer だわん!
}
有界型パラメータ
public class SuperClass {
public <T extends String> void nakigoeExt(T t)
{
System.out.println(t.getClass().getName() + " だわん!");
}
}
型パラメータをStringのみに制限します。
サブタイプの落とし穴
public class SuperClass {
public <T> void nakigoeExt(T t) {
System.out.println(t.getClass().getName() + " だわん!");
}
}
GenericsClass<Number> geneNum = new GenericsClass<Number>();
geneNum.set(new Integer(393)); // OK!!
geneNum.set(new Double(393.39)); // OK!!
NumberのサブクラスであるInteger、Doubleを突っ込む事が出来ます。
public class SuperClass {
public void utagoe(GenericsClass<Number> num) {
System.out.println(num.get());
}
}
utagoe(GenericsClass<Number>)というシグネクチャのメソッドを作った場合、GenericsClass<Integer>、GenericsClass<Double>を渡す事が出来るでしょうか。
public static void main(String[] args) {
GenericsClass<Number> geneNum = new GenericsClass<Number>();
GenericsClass<Integer> geneInt = new GenericsClass<Integer>();
GenericsClass<Double> geneDouble = new GenericsClass<Double>();
SuperClass superCls = new SuperClass();
superCls.utagoe(geneNum); // OK!!
superCls.utagoe(geneInt); // NG!!
superCls.utagoe(geneDouble); // NG!!
}
GenericsClass<Integer>、GenericsClass<Double>は、GenericsClass<Number>のサブタイプでは無い為、引数として渡す事が出来ません。
ワイルドカード
「Numberを含めたNumberのサブタイプである全ての型」を引数にする場合は、下記のようにします。これを「Numberを型の上限とする有界ワイルドカード」と言います。
public class SuperClass {
public void utagoe(GenericsClass<? extends Number> num)
{
System.out.println(num.get());
}
}
public static void main(String[] args) {
GenericsClass<Number> geneNum = new GenericsClass<Number>();
GenericsClass<Integer> geneInt = new GenericsClass<Integer>();
GenericsClass<Double> geneDouble = new GenericsClass<Double>();
SuperClass superCls = new SuperClass();
superCls.utagoe(geneNum); // OK!!
superCls.utagoe(geneInt); // OK!!
superCls.utagoe(geneDouble); // OK!!
}
逆に「Integerを含めたIntegerのスーパータイプである全ての型」を引数にする場合、下記のようにします。これを「Integerを型の下限とする有界ワイルドカード」と言います。
public class SuperClass {
public void utagoe(GenericsClass<? super Integer> num)
{
System.out.println(num.get());
}
}
public static void main(String[] args) {
GenericsClass<Number> geneNum = new GenericsClass<Number>();
GenericsClass<Integer> geneInt = new GenericsClass<Integer>();
GenericsClass<Double> geneDouble = new GenericsClass<Double>();
SuperClass superCls = new SuperClass();
superCls.utagoe(geneNum); // OK!!
superCls.utagoe(geneInt); // OK!!
superCls.utagoe(geneDouble); // NG!!
}
型消去
J2SE5.0の新機能としてGenericsが追加されました。後方互換性を保つために、コンパイラの実装方法として、型消去が用いられる事になりました。
この事から、Javaコンパイラによってジェネリクス型についての情報が実行時に消去されます。
雑感
リフレクションを使えば、何の型に対するジェネリクス型かが判るらしいけど、どうすれば?