web-dev-qa-db-ja.com

ジェネリックスとClass.forName

その名前を使用して、指定されたクラスのインスタンスを作成したいと思います。私のコードを以下に示します。

コンパイラの警告が表示されます。私はこれを正しい方法で行っていますか?クラスの名前を使用してその型のインスタンスを取り戻すことさえ可能ですか?コンパイラが型がどうあるべきかを知る方法はないと思いますか?

public static <T> T create(final String className) {
    try {
        final Class<?> clazz = Class.forName(className);

        //WARNING: Type safety: Unchecked cast from capture#2-of ? to T
        return (T) create(clazz); 
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public static <T> T create(final Class<T> classToCreate) {
    final Constructor<T> constructor;
    try {
        constructor = classToCreate.getDeclaredConstructor();
        final T result = constructor.newInstance();
        return result;
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

ありがとう

22
dogbane

最初の方法は次のようになると思います。

public static <T> T create(final String className, Class<T> ifaceClass) 
throws ClassNotFoundException {
    final Class<T> clazz = Class.forName(className).asSubclass(ifaceClass);
    return create(clazz); 
}

型パラメータを使用してアップキャストタイプキャストを行うことはできません...これらの厄介な型安全性の警告なしに。

ちなみに、これらの警告を無視すると、createメソッドは、呼び出し元が使用する実際の型と互換性のないクラスのインスタンスを作成する可能性があります。これにより、後で予期しないClassCastExceptionが発生する可能性があります。例えばインスタンスが割り当てられたとき。


編集:@Pascalは、このコンパイルを行うにはタイプキャストを追加する必要があることを指摘しています。つまり.

Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass);

残念ながら、また @ SuppressWarningsアノテーションを追加する必要があります。

33
Stephen C

2番目の方法で問題ありません。


ただし、最初のクラス名については、任意のクラス名をパラメーターとして渡すことができます。

このメソッドのポイントは、呼び出し元のコードがコンパイル時に認識しないクラスをインスタンス化することであり、実行時にのみ認識します。

これらの条件では、呼び出し元のコードはタイプパラメータを設定できないため、メソッドをパラメータ化できないため、キャストには意味がありません。

したがって、私はメソッド全体には意味がありませんと言います。


メソッドにいくつかのポイントを与えることができます呼び出し元のコードがクラスのスーパータイプを知っている場合。それをパラメータとして渡すことができ、キャストは可能で意味のあるものになります。

public static <T> T create(final Class<T> superType, final String className) {
  try {
    final Class<?> clazz = Class.forName(className);
    final Object object = clazz.newInstance();
    if (superType.isInstance(object)) {
      return (T)object; // safe cast
    }
    return null; // or other error 
  } // catch and other error code
}
2
KLE

これは、Class.forName(..)がTに対してパラメーター化されていないためだと思います。Eclipseオートコンプリートをトリガーすると、clazz.newInstance()がオブジェクトを返すと想定します。したがって、キャストを保持し、@ SuppressWarningsを追加します。メソッドを適切に使用しない場合(つまり、String str = Utils.create("Java.util.ArrayList");)、ClassCastExceptionが発生しますが、これは正常です。

2
Bozho

ClassNameという名前のタイプを含むようにタイプパラメーターを制限することはできません。したがって、呼び出し元はcreate(String)関数に任意の型を指定できますが、これはもちろん型安全ではありません。

返されたインスタンスがインターフェイスを実装することを静的に強制することはできないため(対応するClassオブジェクトを渡すことで呼び出し元に通知することなく)、ジェネリックスを省略し、呼び出し元が期待するタイプにキャストするようにします。

public static Object create(final String className) {
    try {
        final Class<?> clazz = Class.forName(className);
        return create(clazz); 
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

発信者は次のように書くことができます。

Foo foo = (Foo) create("my.cool.FooBar");

とは対照的に

Foo foo = create("my.cool.FooBar", Foo.class);
1
meriton

上記のコードを機能させることができたとしても、コンストラクターのnewInstance()メソッドは引数がゼロのコンストラクターを想定しています。

クラスに1つがない場合、つまり、作成しようとしているクラスのゼロ引数コンストラクターが明示的にプライベートとして宣言されている場合、またはメソッドが呼び出される場所に応じてパッケージが宣言されている場合、IllegalAccessExceptionが発生します。あなたがそれを機能させるならば、あなたの前提条件に追加する何か。

0
chrisg

興味深いことがわかりました。ジェネリック型でない場合は、Typeを直接Classに変換できます。しかし、私はまだClass.forName("Java.util.List<SomeType>")できません。

import Java.lang.reflect.*;
import Java.util.*;

public class Main {
    public void func(List<List<String>> listlist, Map<String, List<String>> list, String s) {}

    public static void main(String[] args) throws ClassNotFoundException {
        Method m = Main.class.getDeclaredMethods()[1];

        Type t0 = m.getGenericParameterTypes()[0];
        boolean b0 = t0 instanceof Class;

        boolean b01 = ((ParameterizedType)t0).getRawType() instanceof Class;

        Type t1 = m.getGenericParameterTypes()[2];
        boolean b1 = t1 instanceof Class;
    }
}

enter image description here

0
osexp2003