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

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

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

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

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

Если мы напишем

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

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

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

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

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

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

public class ReflectionUtilsTest extends TestCase

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

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

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

Пусть теперь нам нужно из экземпляра класса 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

();

// clazz - текущий рассматриваемый класс Class clazz = actualClass;

while ( true )

// Нужный класс найден. Теперь мы можем узнать, какими типами он параметризован. Type result = genericClasses.pop().getActualTypeArguments()[parameterIndex];

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

if (result instanceof TypeVariable)

if (result instanceof ParameterizedType)

if (result == null )

if (!(result instanceof Class))

return (Class) result; >

public static int getParameterTypeDeclarationIndex(final TypeVariable typeVariable) >

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

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

public class TopicFactory extends AbstractEntityFactory<Topic>

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

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

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

📎📎📎📎📎📎📎📎📎📎