JavaのGenericクラスのパラメーターを学習します

Javaで頻繁にプログラミングしない場合、このトピックはほとんど役に立たないでしょう。 読まないでください:)



最近、次の問題を解決する必要がありました:ジェネリッククラスをパラメーター化するクラスを定義します。



誰かが同様の問題に直面した場合、おそらくすぐに次のようなものを書き込もうとしました:
public class AbstractEntityFactory<E extends Entity> {

public Class getEntityClass() {

return E. class ;

}

}






残念ながら、IDEまたはコンパイラはすぐにエラー(標準コンパイラでは「型変数から選択できません」)を示します。「 E. class 」は有効な構造ではありません。 実際には、一般的なケースでは、プログラムの実行中に、ジェネリッククラスの実際のパラメーターに関する情報が存在しなくなる可能性があります。 したがって、このようなJavaの構築は機能しません。



書いたら
ArrayList <Float> listOfNumbers = new ArrayList <Float>();



次に、 型の消去により listOfNumbersを分析して、これがFloatによってパラメーター化されたArrayListであり、他の何かではないことを確認できません。 残念ながら、Java Genericsはそのように機能します:(



コンパイル時のジェネリッククラスのパラメーターに関する情報がトレースなしで常に失われ、実行時に存在しない可能性はありますか? いいえ、あります。 ただし、クラスに関する情報でのみ、そのクラスはジェネリック親のパラメーターの値を明示的に決定します。 調査中のクラスを選択します。

public class FloatList extends ArrayList <Float>{}



ArrayList <Float> listOfNumbers = new FloatList ();





これで、listOfNumbersのリフレクションを分析すると、これはFloatListクラスのオブジェクトであり、祖先はArrayListであり、FloatList内のこのArrayListはFloatクラスによってパラメーター化されていることがわかります。 Class.getGenericSuperclass()メソッドは、これらすべてを学ぶのに役立ちます。
Class actualClass = listOfNumbers.getClass();

ParameterizedType type = (ParameterizedType)actualClass.getGenericSuperclass();

System. out .println(type); // java.util.ArrayList<java.lang.Float>

Class parameter = (Class)type.getActualTypeArguments()[0];

System. out .println(parameter); // class java.lang.Float






したがって、このパラメーターが明示的に設定されている場合(つまり、パラメーターが継承者の1つのextendsセクション内で定義されている場合)、ジェネリッククラスの実際のパラメーターを見つけることができます。 一般的な形式でパラメータの型を決定する問題を解決することはできませんが、多くの場合、取得したもので十分です。



すべてを別の方法で出力します。
public class ReflectionUtils {

public static Class getGenericParameterClass(Class actualClass, int parameterIndex) {

return (Class) ((ParameterizedType) actualClass.getGenericSuperclass()).getActualTypeArguments()[parameterIndex];

}

}






ソースクラスを書き換えます。

public class AbstractEntityFactory<E extends Entity> {

public Class getEntityClass() {

return ReflectionUtils.getGenericParameterClass( this .getClass(), 0);

}

}






すべて、問題は解決しました! かどうか?..



ExtendedFloatListクラスがFloatListから継承されると仮定しますか? 明らかに、actualClass.getGenericSuperclass()は間違ったクラス(ExtendedFloatListではなくFloatList)を返します。 そして、階層がさらに複雑な場合はどうなりますか? 私たちの方法は価値がありません。 タスクを要約します。 このようなクラスの階層があると想像してみましょう。

public class ReflectionUtilsTest extends TestCase {

// ""



static class A<K, L> {

// String, Integer

}



static class B<P, Q, R extends Collection> extends A<Q, P> {

// Integer, String, Set

}



static class C<X extends Comparable< String >, Y, Z> extends B<Z, X, Set<Long>> {

// String, Double, Integer

}



static class D<M, N extends Comparable<Double>> extends C< String , N, M> {

// Integer, Double

}



static class E extends D<Integer, Double> {

//

}

}






ここで、クラスEのインスタンスから、その祖先Bが2番目のパラメーター(Q)としてStringクラスを受け取ったという情報を取得する必要があるとします。



それで、何が変わったのでしょうか? まず、現在の親を分析するのではなく、クラスの階層を特定の祖先に「上昇」させる必要があります。 第二に、分析されるクラスの最も近い子孫ではなく、「下位」にパラメータを設定できることを考慮する必要があります。 第三に、クラスへのパラメーターの単純なキャストが機能しない場合があります-パラメーター自体がパラメーター化されたクラスである場合があります。 これらすべてを考慮に入れてみましょう...



import java.lang.reflect.GenericDeclaration;

import java.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.lang.reflect.TypeVariable;

import java.util.Stack;



/**

* Alex Tracer (c) 2009

*/

public class ReflectionUtils {



/**

* generic-.

*

* @param actualClass

* @param genericClass ,

* @param parameterIndex

* @return , parameterIndex genericClass

*/

public static Class getGenericParameterClass(final Class actualClass, final Class genericClass, final int parameterIndex) {

// genericClass actualClass.

if (!genericClass.isAssignableFrom(actualClass.getSuperclass())) {

throw new IllegalArgumentException( "Class " + genericClass.getName() + " is not a superclass of "

+ actualClass.getName() + "." );

}



// , genericClass.

// , .

// genericClasses - .



// - .

Stack<ParameterizedType> genericClasses = new Stack<ParameterizedType>();



// clazz -

Class clazz = actualClass;



while ( true ) {

Type genericSuperclass = clazz.getGenericSuperclass();

boolean isParameterizedType = genericSuperclass instanceof ParameterizedType;

if (isParameterizedType) {

// - , - .

genericClasses.push((ParameterizedType) genericSuperclass);

} else {

// . .

genericClasses.clear();

}

// , .

Type rawType = isParameterizedType ? ((ParameterizedType) genericSuperclass).getRawType() : genericSuperclass;

if (!rawType.equals(genericClass)) {

// genericClass .

// .

clazz = clazz.getSuperclass();

} else {

// . .

break ;

}

}



// . , .

Type result = genericClasses.pop().getActualTypeArguments()[parameterIndex];



while (result instanceof TypeVariable && !genericClasses.empty()) {

// - , .



// , .

int actualArgumentIndex = getParameterTypeDeclarationIndex((TypeVariable) result);

// , .

ParameterizedType type = genericClasses.pop();

// .

result = type.getActualTypeArguments()[actualArgumentIndex];

}



if (result instanceof TypeVariable) {

// , .

// - "Type erasure" .

throw new IllegalStateException( "Unable to resolve type variable " + result + "."

+ " Try to replace instances of parametrized class with its non-parameterized subtype." );

}



if (result instanceof ParameterizedType) {

// .

// , .

result = ((ParameterizedType) result).getRawType();

}



if (result == null ) {

// Should never happen. :)

throw new IllegalStateException( "Unable to determine actual parameter type for "

+ actualClass.getName() + "." );

}



if (!(result instanceof Class)) {

// , - - , .

throw new IllegalStateException( "Actual parameter type for " + actualClass.getName() + " is not a Class." );

}



return (Class) result;

}



public static int getParameterTypeDeclarationIndex(final TypeVariable typeVariable) {

GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();



// , .

TypeVariable[] typeVariables = genericDeclaration.getTypeParameters();

Integer actualArgumentIndex = null ;

for ( int i = 0; i < typeVariables.length; i++) {

if (typeVariables[i].equals(typeVariable)) {

actualArgumentIndex = i;

break ;

}

}

if (actualArgumentIndex != null ) {

return actualArgumentIndex;

} else {

throw new IllegalStateException( "Argument " + typeVariable.toString() + " is not found in "

+ genericDeclaration.toString() + "." );

}

}

}






ええと、1行のメソッドはかさばるモンスターに変わりました! :)

何が起こっているのかを理解するのに十分なコメントがあることを願っています;)



したがって、初期クラスを書き直します。

public class AbstractEntityFactory<E extends Entity> {

public Class getEntityClass() {

return ReflectionUtils.getGenericParameterClass( this .getClass(), AbstractEntityFactory. class , 0);

}

}






これで、このコードは正しく動作します:

public class Topic extends Entity {

}



public class TopicFactory extends AbstractEntityFactory<Topic> {

public void doSomething() {

Class entityClass = getEntityClass(); // Topic

}

}






おそらくそれだけです。 最後まで読んでくれてありがとう:)



これはHabréに関する私の最初の投稿です。 批判、コメント、誤りの指摘に感謝します。



更新:階層のどこかにパラメータ化されていないクラスがある場合の状況を正しく考慮するようにコードが修正されました。

Upd2:エラーを指摘してくれたパワーユーザーに感謝します。



Upd3: ソースコードとテストを含むアーカイブ



All Articles