genericsの大事なところ
一番使えるとおもったところ
メソッド単位のgenericsは使い手にも簡単で、API提供も簡単。
public <T> T executeQuery(String sql,Class<T> valueObjectType){
T t = valueObjectType.newInstance();
…
return t;
}
public <T> T changeBeanValue(T t){
…
return t;
}
public static void main(String args[]){
Bean bean = executeQuery("select ...",Bean.class);
bean = changeBeanValue(bean);
}
フレームワークとかで使いそう
extends,superキーワードを使うと、複数の型パラメータ間の関係を定義できる。
多分良く使うのはextends
例)
class BeanFactory<T,U extends T> {
Clazz<U> clazz;
T instance;
public void regist(Class<U> clazz){
this.clazz = clazz;
instance = this.clazz.newInstance(); // ←UはTのサブタイプなので、型に安全にT型で保有できる
}
public T getInstance(){
return T;
}
}
Classクラスが型パラメータになっている意味
型パラメータTがあるおかげで、リフレクションを使った場合でも比較的型に安全なプログラムが組めるようになる。
もしTがなければ、Class#newInstance()を呼んだ戻り値は常にObjectなので、キャストせざるを得ない。
型パラメータTがあるので、Clsssを引数としていったん受けることで、T側を定義できる。これが大きい。
例)
Class<?> clazz = Class.forName("java.lang.String").newInstance(); //うーん、結局Class#forNameではいみがないか?
例)
public T getInstance(Class<T> t,String name){
T t = t.cast(Class.forName(name).newInstance()); //まぁキャストしてるけど…API的にはちゃんとT型で受け取れる。
return t;
}
public static void main(String args[]){
Component component = getInstance(Component.classs,"ComponentImpl"); //うーん。これは本当に型に安全と呼んでいいのか?
}
例)
public T registInstance(Class<T> t,Class<U extends T>){
T t = U.newInstance();
map.put(name,t);
}
public T getInstance(Class<T> t){
return t.cast(map.get(t)); //これならregistIntanceで型を縛っているのでいいはず。
}
public static void main(String args[]){
registInstance(Component.class,ComponetImpl.class);
Component c = getInstandce(Component.class);
}
総称(?)
反則技。こういうのがあるから良く分からなくなるのだが…
型パラメータを指定しなくてもエラーを出さない方法に過ぎない。
genericsはイレイジャ(内部的にObject型で扱い、型パラメータに従ってキャストする仕組み)を使っているので、?にすると結局Object型になってキャストする羽目になる。これでは型安全とはいえないが、、、
例)
List<String> strlist = new ArrayList<String>();
List<Integer> intlist = new ArrayList<Integer>();
// Map<Class<T>,List<T>> map = new HashMap<Class<T>,List<T>>():←こうしたいが、同じMapに指定する型パラメータは当然唯一なので、これは無理
Map<Class<?>,List<?>> map = new HashMap<Class<?>,List<?>>();
map.put(String.class,strlist);
map.put(Float.class,intlist);
// List<Float> list = map.get(Float.class); ←これはできない
List<?> list = map.get(Float.class);
List<Float> list = (List<Float>)map.get(Float.class); ←ClassCastException!
これを防ぐには、結局インタフェースレベルで保障してあげないといけない。
public <T> void add(Class<T> type,T value){
//Class<T> clazz = T.getClass(); ←無理!Class<T>がほしければ明示的にもらわないとだめ。
List<T> list = (List<T>)map.get(type);
if(list == null){
list = new ArrayList<T>();
map.put(type,list); //これでmapのキーのクラスパラメータと、listに指定したテンプレートが一致することが保障される
}
list.add(value);
}
public <T> List<T> getList(T value){
return (List<T>)map.get(value);
}
なんか変だぞ?という気もするけれど、結局genericsの目的は利用者にとって型安全が保障できることなので、それを提供する側のコードが多少いびつというか、おかしなことになっても気にしないのがいいのだろう。こうしておけば使う側は、
public static void main(String args[]){
add(String.class,"foo");
add(String.class,"boo");
add(Integer.class,100);
List<String> list = getList(String.class);
StringBuilder builder = new StringBuilder();
for(String string : list){
builder.append(string);
}
System.out.println(builder.toString());
}
っつーかんじでキャストを気にしなくていい。
ホントは
add("foo");
add("boo");
add(100);
ってできるといいんだけど。
ちなみにもっとよくわかんないのが
<? extends Cloneable>
<? super Cloneable>
これは型パメータがCloneableを実装している何らかの型、もしくはCloneableを実装しているもしくは何も実装していない型を指す総称。総称なので型の集合であり、結局ここに指定される型は唯一ではないので、型パラメータへの値の設定は許容されないらしい。つまり読み取り専用。ここまで来るとだんだんわからなくなるが…
型パラメータを持つインタフェースの実装
これはよい。昔からやりたかったことができる!
例)
public interface ObjectBuilder<T>{
public T build();
}
public class SomeBeanBuilder<SomeBean> implements ObjectBuilder<SomeBean>{
public SomeBean build(){
return new SomeBean();
}
}
例2)
public interface ObjectBuilder<T extends Cloneable>{
public T build();
}
public class SomeBeanBuilder<SomeBean> implements ObjectBuilder<SomeBean>{
public SomeBean build(){
return new SomeBean();
}
}
public <T extends Cloneable> T clone(ObjectBuilder<T> builder){
return builder.build().clone();
}
最終更新:2008年10月05日 03:07