Получить тип универсального параметра в Java с отражением - PullRequest
123 голосов
/ 14 декабря 2009

Можно ли получить тип универсального параметра?

Пример:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}

Ответы [ 16 ]

144 голосов
/ 14 декабря 2009

Одна конструкция, на которую я однажды наткнулся, выглядела как

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Так что, похоже, вокруг меня есть какая-то магическая рефлексия, которую я, к сожалению, не до конца понимаю ... Извините.

124 голосов
/ 20 декабря 2012

Я хочу попытаться объяснить ответ @DerMike, чтобы объяснить:

Во-первых, удаление типа не означает, что JDK удаляет информацию о типе во время выполнения. Это метод, позволяющий проверять типы во время компиляции и совместимости типов во время выполнения на одном языке. Как следует из этого блока кода, JDK сохраняет стертую информацию о типе - она ​​просто не связана с проверенными приведениями и прочим.

Во-вторых, это предоставляет информацию общего типа для универсального класса ровно на один уровень выше иерархии от проверяемого конкретного типа, т.е. абстрактный родительский класс с параметрами универсального типа может найти конкретные типы, соответствующие параметрам его типа, для конкретной конкретной реализации, которую непосредственно наследует от него. Если бы этот класс был неабстрактным и реализованным, или конкретная реализация была на два уровня ниже, это не сработало бы (хотя небольшое колебание могло бы сделать его применимым к любому заранее определенному количеству уровней, превышающему один, или вплоть до самого низкого класса с параметрами универсального типа X и т. д.).

Во всяком случае, к объяснению. Вот снова код, разделенный на строки для удобства:

1# Class genericParameter0OfThisClass = 
2#     (Class)
3#         ((ParameterizedType)
4#             getClass()
5#                .getGenericSuperclass())
6#                    .getActualTypeArguments()[0];

Пусть 'us' будет абстрактным классом с универсальными типами, который содержит этот код. Читая это примерно наизнанку:

  • Строка 4 получает текущий конкретный класс 'Экземпляр класса. Это идентифицирует конкретный тип нашего непосредственного потомка.
  • Строка 5 получает супертип этого класса как Тип; это мы. Поскольку мы параметрический тип, мы можем безопасно привести себя к ParameterizedType (строка 3). Ключ в том, что когда Java определяет этот объект Type, она использует информацию о типе, присутствующую в дочернем элементе, чтобы связать информацию о типе с нашими параметрами типа в новом экземпляре ParameterizedType. Так что теперь мы можем получить доступ к конкретным типам наших дженериков.
  • Строка 6 получает массив типов, сопоставленных с нашими обобщениями, в порядке, объявленном в коде класса. Для этого примера мы вытащим первый параметр. Это возвращается как Тип.
  • В строке 2 приведен окончательный тип, возвращаемый классу. Это безопасно, потому что мы знаем, какие типы могут принимать параметры универсального типа, и можем подтвердить, что все они будут классами (я не уверен, как в Java можно было бы получить универсальный параметр, у которого нет экземпляра класса связано с этим, собственно).

... и это все. Поэтому мы помещаем информацию о типе из нашей собственной конкретной реализации обратно в себя и используем ее для доступа к дескриптору класса. мы могли бы удвоить getGenericSuperclass () и пройти два уровня или исключить getGenericSuperclass () и получить значения для себя как конкретный тип (предостережение: я не тестировал эти сценарии, они еще не подошли для меня).

Становится сложно, если ваши конкретные дети находятся на произвольном количестве прыжков, или если вы конкретны и не окончательны, и особенно сложно, если вы ожидаете, что у любого из ваших (переменно глубоких) детей будут свои собственные дженерики. Но обычно вы можете придумать эти соображения, так что вы получите большую часть пути.

Надеюсь, это помогло кому-то! Я признаю этот пост древним. Я, вероятно, отрежу это объяснение и оставлю его для других вопросов.

20 голосов
/ 14 марта 2011

На самом деле я получил это на работу. Рассмотрим следующий фрагмент:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"

     }
 }

Я использую JDK 1.6

16 голосов
/ 14 апреля 2013

На самом деле есть решение, применяя «анонимный класс» трюк и идеи из Супер Типовых токенов :

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

Хитрость заключается в создании анонимного класса , new ArrayList<SpiderMan>() {} вместо исходного (простого) new ArrayList<SpiderMan>(). Использование аномального класса (если возможно) гарантирует, что компилятор сохранит информацию об аргументе типа SpiderMan, заданную параметру типа List<?>. Вуаля!

7 голосов
/ 14 декабря 2009

Из-за стирания типа единственный способ узнать тип списка - передать тип в качестве параметра методу:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}
6 голосов
/ 07 сентября 2015

Приложение к ответу @ DerMike для получения универсального параметра параметризованного интерфейса (используя метод # getGenericInterfaces () внутри метода Java-8 по умолчанию, чтобы избежать дублирования):

import java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}
6 голосов
/ 14 декабря 2009

Нет, это невозможно. Из-за проблем с обратной совместимостью дженерики Java основаны на стирании типа , т.е. во время выполнения все, что у вас есть, это неуниверсальный объект List. Есть некоторая информация о параметрах типов во время выполнения, но она содержится в определениях классов (то есть вы можете спросить " какой универсальный тип использует определение этого поля? "), а не в экземплярах объекта.

5 голосов
/ 14 февраля 2012

Как указал @bertolami, нам невозможно определить тип переменной и получить ее будущее значение (содержимое переменной typeOfList).

Тем не менее, вы можете передать класс в качестве параметра для него следующим образом:

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

Это более или менее то, что делает Google, когда вам нужно передать переменную класса в конструктор ActivityInstrumentationTestCase2 .

3 голосов
/ 14 декабря 2009

Нет, это невозможно.

Вы можете получить универсальный тип поля, учитывая, что класс является единственным исключением из этого правила, и даже это немного хакерское.

См. Знание типа универсального в Java для примера этого.

0 голосов
/ 06 апреля 2018

Быстрый ответ на вопрос - нет, вы не можете, из-за стирания универсального типа Java.

Более длинный ответ будет таким: если вы создали свой список следующим образом:

new ArrayList<SpideMan>(){}

Тогда в этом случае универсальный тип сохраняется в универсальном суперклассе нового анонимного класса выше.

Не то чтобы я рекомендовал делать это со списками, но это реализация слушателя:

new Listener<Type>() { public void doSomething(Type t){...}}

И поскольку экстраполяция универсальных типов суперклассов и суперинтерфейсов изменяется между JVM, универсальное решение не такое прямолинейное, как могут предположить некоторые ответы.

Вот теперь я это сделал.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...