GenericsはJava2 SDK 1.5のバージョンから使える機能で、
Objectクラスを利用した汎用的に使えるコードを
作る時と、利用する時に、
< と、>の指定で型の整合性をチェックする機能です。
(Javaテンプレートと呼ばれることもあるようですが、コンパイラの型チェック機構として働くもので、
C++言語のプリコンパイル的なテンプレートと本質的に異なります)
具体的には、汎用クラスを作る時に、
<総称的名前>で指定します。
そして、汎用クラスを利用する時に、
<具体的名前>を指定して使う形態です。
以下に簡単な例を示します。
これは任意の配列に対して、オブジェクト内のdataフィールドで設定するクラスのプログラムです。
FillSetterのクラス名で作っています。
以下ではこの汎用コードを作る時に書く
<総称的名前>の
< >内文字列に Tの文字を使っていますが、任意の文字列が使えます。
一般に大文字を使い、T以外にも Eなどが慣例的に使われます。
<総称的名前>の書く位置は、クラス名の直後です。
public class FillSetter <T> { T data; public FillSetter(T d){//コンストラクタ this.data = d; } void fillData( T []a ){ for(int i=0; i < a.length; i++) a[i] = this.data;//配列全てを設定する } T getData (){ return this.data; } }
<T>の記述でTのクラス名があると仮想的に示しています。
そして、Tのクラス名を利用してそのクラス内をプログラムしています。
(Tが使える範囲は、このクラス内だけです)
こんどは、これを利用するプログラムの例です。
FillSetterクラスを利用する時にTの
クラス名を<具体的名前>で指定して使っています。
public class Test {
public static void main(String[] args)
{
String [] sa = new String[3];
Double [] da = new Double[2];
FillSetter<String> s1 = new FillSetter<String>("AAA");
s1.fill(sa);//sa配列全体を設定
for(int i=0; i < sa.length; i++){
System.out.println( sa[i].toString() );//確認表示
}
FillSetter<Double> s2 = new FillSetter<Double>( new Double(1.23) );
s2.fill(da);//da配列全体を設定
for(int i=0; i < da.length; i++){
System.out.println( da[i].toString() );//確認表示
}
//s1 = s2;//【★】
}
}
これより分かるように、FillSetterクラスのオブジェクトで、String型やDouble型の配列全体を 希望の値に設定しています。このように任意のオブジェクトに対して、 配列要素の全部を希望の値で設定できる汎用クラスが出来上がっています。 参考に実行例を以下に示します。(なおdoubleは、参照型でないため、直接にdataへ 記憶できません。そこでdoubleを包んだクラスで、Doubleという既存のクラスを使っています。)
D:\java>java Test AAA AAA AAA 1.23 1.23 D:\java>
さて、//s1 = s2;//【★】の部分のコメントを 外してs1 = s2;//【★】の記述がある場合、どうなるでしょうか? 次のコンパイルエラーが得られ、これがGenericsを使った利点です。
D:\java>javac Test.java Test.java:19: 互換性のない型 検出値 : FillSetter期待値 : FillSetter s1 = s2;//【★】 ^ エラー 1 個 D:\java>
このような使い方の間違いをコンパイラでチェックできることが、Genericsを使った利点なのです。
この内部で作られるコードは次のようにObject型で作られて実現されます。
public class FillSetter { Object data; public FillSetter(Object d) { this.data = d; } void fill(Object[] a) { for(int i=0; i < a.length; i++) a[i] = this.data; } Object getData() { return this.data; } } |
public class Test { public static void main(String[] args){ Object[] sa = new String[3]; Object[] da = new Double[2]; FillSetter s1 = new FillSetter("AAA"); s1.fill(sa); for (int i = 0; i < sa.length; i++) { System.out.println(sa[i].toString()); } FillSetter s2 = new FillSetter(new Double(1.23)); s2.fill(da); for (int i = 0; i < da.length; i++) { System.out.println(da[i].toString()); } //s1 = s2;//【★】 } } |
つまり、FillSetter<String> s1や、
FillSetter<Double> s2で、
指定しても、対応する新しい型が作られる訳ではなく、内部では、Object型クラスで作られるのです。
そしてこのコードの書き方が、Genericが使えるJDK1.4バージョン以前の作り方だったのです。
さて、コメントを外してs1 = s2;//【★】の記述がある場合、どうなるでしょうか?
s1もs2もObjectなので、コンパイルエラーが起きないのです。そして、この問題が
Genericsの仕様追加の理由になっています。
上記はクラスに対してGenericsを使った説明です。
これ以外に個々のメソッドに対しても使えるので以下に説明します。
メソッドに対してGenericを使う<総称的名前>の書く位置は、
戻り値表現位置の直前です。そして、総称的名前の適用範囲はそのメソッドの中だけです。
以下は、Testクラスの中で、任意型の配列の指定される2つの添え字が示す要素を交換するメソッドを作り、
それをmainで利用するプログラムの例です。
<T>の記述でTのクラス名があると仮想的に示しています。
そして、Tのクラス名を利用してswapメソッドを作っています。
(Tが使える範囲は、このswapメソッド内だけです)
クラスの時と違い、
そのコードを利用する時に、< >の中に
具体的名前を指定して使う形態ではありません。
通常の通りに、希望の実引数を指定するだけです。
mainでswapメソッドを利用する時にTの引数に
saのString[]を指定したり、
daのDouble[]を指定して使っています。
public class Test { public static <T> void swap(T []a, int i1, int i2){ T temp = a[i1];//交換作業用 a[i1] = a[i2]; a[i2] = temp; } public static void main(String[] args){ String[] sa = new String[] { "ABC", "xy", "123" }; Double[] da = new Double[] { new Double(1.1), new Double(2.2) }; Test.swap(sa, 0, 2);// sa[0]⇔sa[2] Test.swap(da, 0, 1);// da[0]⇔da[1] } }