Generics(Java)
Generics
Generics(ジェネリクス)は、Java5.0の目玉の一つです。このGenericsとAutoboxing/Unboxing、拡張for文によって、プログミングが随分とすっきりするようになりました。
また、これまでObject型しか格納できないCollectionフレームワークには、間違ったクラスを格納してしまった場合、それが(Collectionからの取り出し時の)ClassCastExceptionという実行時例外(Runtime Exception)でしか捕捉出来ないといった問題がありました。
Genericsの導入によって、Collectionに(格納する)「型」を宣言することで、間違った型を格納することが「コンパイルエラー」として発現させることができるようになりました。これにより、コード品質の向上が見込めます。
簡単なGenericsのサンプルプログラム
ListとMapを使うコーディングかんたんなサンプルを作ってみます。まず、1.4系で以下のようなコードを書いてみます。
package test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExampleGenerics1 {
public static void main(String[] args) {
// Listに整数1を追加する。
List aList = new ArrayList();
aList.add(new Integer(1)); // (A) 1をラッパークラスIntegerでラップする。
System.out.println("listed value = " + aList.get(0));
// 上のListから整数1を取り出す。
// (B) 取り出してラッパークラスにCastした後、1に足す。
int sum = 1 + ((Integer)aList.get(0)).intValue();
System.out.println("test sum = " + sum);
// 整数をキーにするMapを作る。
Map map = new HashMap();
map.put(new Integer(1),"いちご"); // (C) キーとなる整数をラップする。
map.put(new Integer(2),"ばなな");
// (D) 1番目を取り出すために1をIntegerでラップして、取り出す。
System.out.println("first element is " + map.get(new Integer(1)));
}
}
これを実行すると以下のような結果が得られます。
listed value = 1
test sum = 2
first element is いちご
これと同じ結果を得られるコードをGenerics+Autoboxing/Unboxingを使って記述してみます。
package test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SampleGenerics1 {
public static void main(String[] args) {
// Listに整数1を追加する。
List<Integer> aList = new ArrayList<Integer>(); // (a) GenericsでListの型を宣言。
aList.add(1); // (b) Autoboxingで整数をそのままadd。
System.out.println("listed value = " + aList.get(0));
// 上のListから整数1を取り出す。
int sum = 1 + aList.get(0); // (c) Auto-unboxingで整数をそのまま取り出す。
System.out.println("test sum = " + sum);
// 整数をキーにするMapを作る。
Map<Integer,String> map = new HashMap<Integer,String>(); // (d) GenericsでMapを宣言。
map.put(1,"いちご"); // (e) Auto-unboxingで整数をKeyにそのまま取り出す。
map.put(2,"ばなな");
System.out.println("first is " + map.get(1));
}
}
GenericsはCollectionに対して、「それに格納されるオブジェクトの型」を宣言する機構を提供します。
上の(a)では、Listの中身はIntegerだぞと宣言しています。これと同様に、(d)ではMapに対して2つ取る引数を、それぞれIntegerとStringと宣言しています。
Genericsによって、このように「型」がプログラムレベルで規定されてしまうと、(b)、(c)、(e)のようにAutoboxing/Unboxingによって、プリミティブ型⇔参照型の変換が自動的に行えるようになります。
これらの一連の機構が、(実行時ではなく)コンパイルのレベルで機能するところも良い点です。なぜなら、前にも述べましたように、実行時例外を防止できること、Castという(わずらわしい上に)コスト高の処理を行わなくて良いからです。
Genericsと型引数
それではGenericsの意味するところを、もうちょっとよく見てみましょう。上のサンプルで、Genericsを利用した箇所は以下の2箇所でした。
List<Integer> aList = new ArrayList<Integer>(); // (a) GenericsでListの型を宣言。
…
Map<Integer,String> map = new HashMap<Integer,String>(); // (d) GenericsでMapを宣言。
まず、これを見て分かることは、
- Collectionフレームワークのインターフェイス(List、Map)と、実装クラス(ArryaList、HashMap)に<E>(Eは「型」を表す)という引数を受け取るような変更が加えられたこと。(これを「型変数」といいます)
- 実装クラス名<E>()がデフォルトのコンストラクタであること。
実際、Java5.0ではCollectionフレームワークのインターフェイスとして、Queueインターフェイス(LinkedListが一つの実装クラスになっています)が追加され、Setインターフェイスとともに「型引数」を受け取るような大幅な修正が加えられています。
また、上の事柄からは以下のことが(ほぼ必然的に)導かれます。
- 「型」をインターフェイスやクラスの引数に取っている以上、内部のメソッドにも型を引数ととることを許している。
- 引数として渡す「型」は実装クラスではなく、抽象クラス、もしくはインターフェイスを指向すべきこと。それにより、クラス間の祖結合を指向できる。
実際、上の「メソッドにも型を引数にとること」を含めた、上の仕様は全てのユーザープログラムに対しても許されるような言語拡張がなされています。
つまり、
- すべてのインターフェイス、クラス、メソッドには「型」を引数としてとることが許される。
実際には、「型引数」の渡し方として<E>だけではなく、<E extends T>という渡し方も可能となっており、Java5.0での言語仕様の拡張が大変大きなものであったことが分かります。
もっと突っ込んだGenericsの使い方
Genericsと型引数の応用(Java)2007年02月28日(水) 09:04:01 Modified by wanderingse