Java get type parameters java

Узнаем параметр Generic-класса в Java

Недавно понадобилось решить следующую задачу: определить класс, которым параметризован generic-класс.

Если кто-то сталкивался с подобной задачей, то наверное также сразу попробовал написать что-то вроде этого:

Увы, IDE либо компилятор сразу укажут вам на ошибку («cannot select from a type variable» в стандартном компиляторе): » E. class » — не является допустимой конструкцией. Дело в том, что в общем случае во время исполнения программы информации о реальных параметрах нашего generic-класса может уже и не быть. Поэтому такая конструкция в Java не может работать.

то из-за стирания типов мы не можем анализируя listOfNumbers узнать, что это — ArrayList параметризованный именно Float, а не чем-то еще. К сожалению Java Generics работают именно так 🙁

Неужели информации о параметрах generic-классов при компиляции всегда теряется бесследно и не существует во время выполнения? Нет, существует. Но только в информации о классе, который явно определяет значение параметра в его generic-родителе. Выделим исследуемый класс:

Теперь, если мы будем анализировать через отражения listOfNumbers, мы сможем узнать, что это объект класса FloatList, для которого предком является ArrayList и этот ArrayList внутри FloatList был параметризован классом Float. Узнать всё это нам поможет метод Class.getGenericSuperclass().

Class actualClass = listOfNumbers.getClass();
ParameterizedType type = (ParameterizedType)actualClass.getGenericSuperclass();
System. out .println(type); // java.util.ArrayList
Class parameter = (Class)type.getActualTypeArguments()[0];
System. out .println(parameter); // class java.lang.Float

Таким образом, теперь мы можем узнать актуальный параметр generic-класса, если этот параметр был задан явным образом (то есть параметр определен внутри секции extends одного из наследников). Пусть мы не можем решить проблему определения типа параметра в общем виде, но во многих случаях даже того, что мы получили — достаточно.

Читайте также:  Функциональный интерфейс java пример

Вынесем всё в отдельный метод:

public class ReflectionUtils public static Class getGenericParameterClass(Class actualClass, int parameterIndex) return (Class) ((ParameterizedType) actualClass.getGenericSuperclass()).getActualTypeArguments()[parameterIndex];
>
>

public class AbstractEntityFactory public Class getEntityClass() return ReflectionUtils.getGenericParameterClass( this .getClass(), 0);
>
>

Всё, проблема решена! Или нет.

Предположим, что от FloatList будет унаследован класс ExtendedFloatList? Очевидно, что actualClass.getGenericSuperclass() вернет нам уже не тот класс, который надо (FloatList вместо ExtendedFloatList). А если иерархия будет еще сложнее? Наш метод оказывается никуда не годным. Обобщим нашу задачу. Пркдставим, что у нас есть такая иерархия классов:

public class ReflectionUtilsTest extends TestCase // В комментариях приведены «реальные» параметры

static class A // String, Integer
>

static class B extends A // Integer, String, Set
>

Пусть теперь нам нужно из экземпляра класса E достать информацию о том, что его предок B в качестве второго параметра (Q) получил класс String.

Итак, что изменилось? Во-первых, теперь нам нужно анализировать не непосредственного родителя, а «подняться» по иерархии классов до определенного предка. Во-вторых, нам нужно учитывать, что параметры могут быть заданы не в ближайшем наследнике анализируемого класса, а «ниже». В-третьих, простой каст параметра к Class может не пройти — сам параметр может быть параметризованным классом. Попробуем всё это учесть…

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 genericClasses = new Stack();

// 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.» );
>

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() + «.» );
>
>
>

Ухх, наш метод «в одну строчку» превратился в громоздкого монстра! 🙂
Надеюсь комментариев достаточно, чтобы понять происходящее 😉

Итак, перепишем наш начальный класс:

public class AbstractEntityFactory public Class getEntityClass() return ReflectionUtils.getGenericParameterClass( this .getClass(), AbstractEntityFactory. class , 0);
>
>

public class TopicFactory extends AbstractEntityFactory public void doSomething() Class entityClass = getEntityClass(); // Вернет Topic
>
>

На этом пожалуй всё. Спасибо что дочитали до конца 🙂

Это мой первый пост на Хабре. Буду благодарен за критику, замечания и указания на ошибки.

Upd: код исправлен для корректного учета ситуации, когда где-то в иерархии присутствует непараметризованный класс.
Upd2: спасибо пользователю Power за указание на ошибки.

Источник

Оцените статью