📜 ⬆️ ⬇️

Recognize the Generic Class Parameter in Java

If you do not program in Java very often, then this topic is most likely to be useless for you. Do not read it :)

Recently it was necessary to solve the following problem: to determine the class, which is parameterized by the generic class.

If someone faced a similar task, then he probably also immediately tried to write something like this:
public class AbstractEntityFactory<E extends Entity> {
public Class getEntityClass() {
return E. class ;
}
}

Alas, the IDE or the compiler will immediately indicate to you the error (“cannot select from a type variable” in the standard compiler): “ E. class ” is not a valid construction. The fact is that in the general case, during the execution of a program, information about the real parameters of our generic class may no longer exist. Therefore, such a construction in Java cannot work.
')
If we write
ArrayList <Float> listOfNumbers = new ArrayList <Float>();
then, due to the erasure of types, we cannot analyze the listOfNumbers to find out that this is an ArrayList parameterized by Float, and not by anything else. Unfortunately, Java Generics work this way: (

Is information about the parameters of generic classes always lost without a trace during compilation and does not exist at runtime? No, there is. But only in the information about the class, which explicitly defines the value of the parameter in its generic parent. Select the class under study:
public class FloatList extends ArrayList <Float>{}
ArrayList <Float> listOfNumbers = new FloatList ();

Now, if we analyze through the listOfNumbers reflections, we can find out that this is an object of the FloatList class for which the ancestor is an ArrayList and this ArrayList inside the FloatList has been parameterized by the Float class. The Class.getGenericSuperclass () method will help us learn all this.
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

Thus, now we can find out the actual parameter of the generic class if this parameter was explicitly specified (that is, the parameter is defined inside the extends section of one of the heirs). Suppose we cannot solve the problem of determining the type of a parameter in a general form, but in many cases even what we received is enough.

We put everything in a separate method:
public class ReflectionUtils {
public static Class getGenericParameterClass(Class actualClass, int parameterIndex) {
return (Class) ((ParameterizedType) actualClass.getGenericSuperclass()).getActualTypeArguments()[parameterIndex];
}
}

Rewrite our original class:
public class AbstractEntityFactory<E extends Entity> {
public Class getEntityClass() {
return ReflectionUtils.getGenericParameterClass( this .getClass(), 0);
}
}

Everything, the problem is solved! Or not?..

Suppose that the class ExtendedFloatList is inherited from FloatList? Obviously, actualClass.getGenericSuperclass () will return to us the wrong class (FloatList instead of ExtendedFloatList). And if the hierarchy will be even more difficult? Our method turns out to be useless. Summarize our problem. Suppose that we have the following class hierarchy:
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> {
//
}
}

Let now we need from the instance of class E to get the information that its ancestor B received the class String as the second parameter (Q).

So what has changed? First, now we need to analyze not the immediate parent, but “climb” along the class hierarchy to a certain ancestor. Secondly, we need to take into account that the parameters can be set not in the nearest heir of the class being analyzed, but “below”. Third, a simple caste of a parameter to Class may not pass — the parameter itself may be a parameterized class. Let's try to take all this into account ...

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() + "." );
}
}
}

Uhh, our "one line" method has become a bulky monster! :)
I hope comments are enough to understand what is happening;)

So, we rewrite our initial class:
public class AbstractEntityFactory<E extends Entity> {
public Class getEntityClass() {
return ReflectionUtils.getGenericParameterClass( this .getClass(), AbstractEntityFactory. class , 0);
}
}

Now this code will work correctly:
public class Topic extends Entity {
}

public class TopicFactory extends AbstractEntityFactory<Topic> {
public void doSomething() {
Class entityClass = getEntityClass(); // Topic
}
}

This is probably all. Thank you for reading to the end :)

This is my first post on Habré. I would be grateful for criticism, comments and indications of errors.

Upd: the code is corrected for the correct consideration of the situation when somewhere in the hierarchy there is a non-parameterized class.
Upd2: Thanks to the Power user for pointing out errors.

Upd3: archive with source codes and tests .

Source: https://habr.com/ru/post/66593/


All Articles