ふと訪れたblogで
共変戻り値(covariant return types)を話題にしていたのですが、
「コレ、JavaSE5.0からだったっけ?1.4だったような」、と思って調べるとやっぱり5.0でした orz
調べるとかつのりさんところ(
共変戻り値)で書いているなぁ…。
自分の中でなぜか1.4だと思い込んでいる様子。この記憶はどこからやってきたのか…。
このあたりを見て、そういえばジェネリクスとリフレクションの話題があったなぁと思いだしたのでひとつ小噺を。
ジェネリクスの具象化とメソッドシグニチャ
さて、気を取り直して、この共変なオーバーライドですが、ジェネリクスの実装にはなくてはならないものです。
public interface Sample<T> {
T hoge();
void piyo(T t);
}
といったジェネリクス型パラメータを持つinterfaceがあったとして
public class SampleImpl implements Sample<String>{
public String hoge() {
return null;
}
public void piyo(String t) {
}
}
というようにimplements時にStringとすると、hoge()の戻り型はStringクラスになるわけです。
これは共変な戻り値になっているわけですね。
ジェネリクスのメソッドをリフレクションしたら
さて、先ほどのコード、piyo()がオーバーロードになっているのがわかりますか?
インターフェースの段階ではジェネリクス型パラメータのTが引数とされていました。
これは実際的にはObject型となってしまうのですが、そうするとSampleImplのpiyo(String)は
オーバーライドではなくオーバーロードになってしまいます。
SampleImpl impl = new SampleImpl();
Method m = impl.getClass().getMethod("piyo", Object.class);
m.invoke(impl, "param");
ところが、上記のようにリフレクションでpiyo(Object)を呼び出すと、ちゃんとpiyo(String)が実行されます。
これはどうしたことか!?
作られるclassの中をのぞいてみると面白いことがわかります。
// Method descriptor #19 (Ljava/lang/String;)V
// Stack: 0, Locals: 2
public void piyo(java.lang.String t);
// Method descriptor #22 (Ljava/lang/Object;)V
// Stack: 2, Locals: 2
public bridge synthetic void piyo(java.lang.Object arg0);
引数がStringのものと、Objectのものとふたつ作られているんですね。
"bridge synthetic"という修飾がついています。
この元となったinterfaceのメソッドシグネチャ(メソッドを特定するためのメソッド名と引数型の組み合わせのこと)と同等のシグネチャで宣言されるメソッドが
ブリッジとなってpiyo(String)を呼び出しているのです。
このため、ジェネリクスをimplementsする際に具現化していても、interfaceや抽象クラスで宣言された
メソッドシグニチャを使ってMethodを取得してinvokeすることができるのです。
投稿日時 : 2008年6月16日 15:07