##Generic Programming とは
プログラミングをしていると複数の型でクラスやメソッドを実装したいと思う。例えばArrayListをint用double用String用とそれぞれ実装することがばかげていることはすぐにわかるだろう. ArrrayList<T>はGenericProgrammingの一例で, 様々な型パラメータのリストを同じように扱えるのでとても有用である.ArrayList<T>のように<>で囲って複数の型パラメータをもつクラスやメソッドを作ることをGenericProgrammingと呼ぶ.
##構成
この記事はまず, 基本的なGenericClass, GenericMethodの実装と使い方を説明する. そのあと, GenericProgramingを制限する方法, 柔軟に扱う方法を紹介する. 最後に, JavaのGenericProgrammingの注意点をまとめる.
##Generic Class
GenericClassの簡単な実装.
public class Entry<K, V>{ // point 1
private K key; // point 2
private V value;
public Entry(K key, V value){ // point 2
this.key=key;
this.value=value;
}
public K key(){return key;} // point 2
public V value(){return value;}
}
- クラス定義の時, クラス名の次に<>で囲った複数の型パラメータを宣言する. EntryでK, Vが宣言されているが, KやVに意味はない. javaの慣習で型パラメータにT,E,K,V,X,R,U,T1,T2の文字を用いる.
- 型パラメータはメンバ変数, コンストラクタ, インスタンスメソッドの返り値の型として使える.
Entry<Integer, String> id = new Entry<String, Integer>(1001, "hitoshi watanabe"); //1
Entry<Integr, String> id2 = new Entry<>(1002, "taro yamada"); //2
var id3 = new Entry<Integer, String>(1003, "hideo kojima"); //3
- これが原型.
- 左辺で型パラメータを宣言すると, 右辺の型パラメータはコンパイラが推論する.
- 2の逆で, 左辺をコンパイラが推論する書き方.
型パラメータにプリミティブ型を用いることはできないので,intではなくボックスされたIntegerを用いる.
##Generic Method
型パラメータはインスタンスに依存するので, staticメソッドで型パラメータを使用したいときはインスタンスとは無関係の型パラメータを宣言する必要がある.
public class Arrays {
public static <T> void swap(T[] array, int i, int j){ // point 1
T tmp = array[i]; //point 2
array[i]=array[j];
array[j]=tmp;
}
}
- GenericMethodを宣言する時は返り値型の前に<>で型パラメータを指定する.
- GenericMethod内では型パラメータを型として扱える.
String[] member = ...;
Arrays.<String>swap(member, 0, 1); //1
Arrays.swap(member, 0, 1); //2
- これが原型
- 第一引数のmemberからswapの型パラメータをコンパイラが推論してくれる
##GenericProgramingの制限
Array.swapのように十分に抽象的な操作は型を制限しない<T>という型パラメータを宣言していいだろう. しかしもっと限定的な操作を行いたいとき, 型パラメータに要求を満たす型のサブタイプのみが入ってくるように制限を設ける必要がある.
例として AutoClosableインターフェースを実装したクラスのArrayListを引数としてその要素をすべてcloseしたいときの実装を紹介する.
public static <T extends AutoClosable> void closeAll(ArrayList<T> elems) throws Exception{ // point 1
for(T e : elems) e.close(); // point 2
}
- <Any extends interface/class>でinterfaceを実装したAny, またはclassを継承したAnyに型パラメータを制限できる.従って, このGenericMethodはAutoClosableのサブタイプしか型パラメータにできない.
- このGenericMethodの型パラメータがAutoClosableを実装していることが担保されているので, AutoClosableが提供するcloseメソッドを呼び出すことができる.
<T>が宣言されているとき, コンパイラは<T extends Object>に補完する. javaにおいて全てのクラスは祖先にObjectクラスを持つため, Objectクラスが提供するメソッドは使用できる.
closeAllメソッドはGenericMethodにする必要はない
public static void closeAll(AutoClosable[] elems){
for(AutoClosable e : elems) e.close();
}
##GenericProgrammingの柔軟性向上
型パラメータ付きのインスタンスを引数に取るメソッドを実装したいとき, 引数に取るインスタンスのサブタイプ, スーパータイプも引数として許容したいと思うことは普通である.しかし,
```BadSample.java
public static void printName(ArrayList<Employee> staff){
for(Employee e : staff) System.out.println(e.getName());
}
BadSample.javaのように引数をArrayListにすると, Employee型のArrayListしか引数として受け取れなくなる. つまり, Employeeを継承したサブクラスのインスタンスのArrayListはこのメソッドで使えない. それではメソッドとしての柔軟性が十分ではないためWildCardという概念を用いる.
WildCardには3種類あるので, それぞれ説明する.
###SubType WildCard
BadSample.javaではEmployeeを継承したクラスを引数に取れなかったので, その問題を解消するために用いるのがSubType WildCardだ.
public static void printName(ArrayList<? extends Emplyee> staff){ // point 1
for(Employee e : staff) System.out.println(e.getName());
}
<? extends hoge>でhogeのsubtype型パラメータのインスタンスを引数として受け取れるようになる. hogeのsubtypeはポリモーフィズムでhogeとして扱うことができる. その時, 用いるメソッドがオーバーライドされていれば, オーバーライドされたメソッドが呼び出される.