続・推論された型パラメータからClassを得る(JDKのコンパイラでは動作しなかった・・orz)
calicoさんより、推論された型パラメータからClassを得るのコードについてご指摘を頂いたので(ありがとうございます!)、いろいろと検証してみた。結論としては、上記日記のコードはEclipseのコンパイラでコンパイルしたクラスでしか期待通りの動作とならないようだ。
日記の検証で使用した以下のコードを、
package generics; import java.util.LinkedList; import java.util.ArrayList; public class CreateInstance { public static void main(String[] args) throws InstantiationException, IllegalAccessException { ArrayList<String> list = create(); } /** * 指定された型のインスタンスを生成する。 * * @param <T> 型。デフォルトコンストラクタを持つこと。 * @param src ※指定しないでください。 * @return インスタンス * @throws InstantiationException インスタンス化できなかった場合 * @throws IllegalAccessException アクセス権が内場合 */ static <T> T create( @Deprecated T... src ) throws InstantiationException, IllegalAccessException { Class<T> cl = (Class<T>) src.getClass().getComponentType(); return cl.newInstance(); } }
JDK1.6.0_07付属のコンパイラでコンパイルしたクラスと、Eclipse(Eclipse Java Compiler 0.793_R33x, 3.3.2)でコンパイルしたものをそれぞれjavapにかけた場合以下の結果となる。(main関数のみ抜粋)
Eclipseでコンバイルしたクラス:
... public static void main(java.lang.String[]) throws java.lang.InstantiationException, java.lang.IllegalAccessException; Signature: ([Ljava/lang/String;)V Code: 0: iconst_0 1: anewarray #21; //class java/util/ArrayList ★ 4: invokestatic #23; //Method create:([Ljava/lang/Object;)Ljava/lang/Object; 7: checkcast #21; //class java/util/ArrayList 10: astore_1 11: return ...
... public static void main(java.lang.String[]) throws java.lang.InstantiationException, java.lang.IllegalAccessException; Signature: ([Ljava/lang/String;)V Code: 0: iconst_0 1: anewarray #2; //class java/lang/Object ★ 4: invokestatic #3; //Method create:([Ljava/lang/Object;)Ljava/lang/Object; 7: checkcast #4; //class java/util/ArrayList 10: astore_1 11: return ...
ポイントは★をつけた部分のコードで、
このため、JDK付属のコンパイラでコンパイルしたクラスでは、create()メソッド内の
Class<T> cl = (Class<T>) src.getClass().getComponentType();
で、java.lang.Objectのクラスが返されるため、ClassCastExceptionとなってしまう。
どっちのコンパイラの挙動が正しいのかまでは調べられていないけど、JDKのコンパイラで使えないなら実質使えないなー。これは使えるテクニックだ!と思っていただけに残念。というかそもそも、EclipseのコンパイラってJDKのモノと違うのかー。それすら知らなかった・・・。orz。