Отримайте тип загального параметра на Java з відображенням


143

Чи можливо отримати тип загального параметра?

Приклад:

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

Відповіді:


156

Одна конструкція, яку я одного разу натрапив на вигляд

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

Тож, мабуть, є якась магія роздумів, яку я, на жаль, не цілком розумію ... Вибачте.


2
Але декларуйте це як абстрактне. коли ви хочете його використовувати, тоді підкласируйте його в рядок.
Snicolas

42
Незважаючи на те, що це дуже старе і чомусь прийнято , я спростував, тому що це просто не відповідає на питання. Це не дало б "SpiderMan" у прикладі, наведеному у питанні. Це, безсумнівно, корисно в деяких ситуаціях, але це не працює на питання, яке було задано.
Джон Скіт

15
Ніхто, хто зайшов так далеко, не збирається прийняти відповідь "Це не можна зробити". Ми тут, бо відчайдушні.
Аддісон

14
Я отримую цей виняток: Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType не впевнений, що таке обмеження.
Блюдо

4
Як і @Dish, я щойно отримуюjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Кенні Віланд

133

Я хочу спробувати розбити відповідь від @DerMike, щоб пояснити:

По-перше, стирання типу не означає, що JDK усуває інформацію про тип під час виконання. Це метод, який дозволяє перевіряти тип компіляції та час сумісності типу співіснування на одній мові. Як випливає з цього блоку коду, JDK зберігає стерту інформацію про тип - вона просто не пов’язана з перевіреними кастрами та ін.

По-друге, це забезпечує загальну інформацію про тип для родового класу рівно на один рівень вгору по спадковості від конкретного типу, що перевіряється - тобто абстрактний батьківський клас із загальними параметрами типу може знайти конкретні типи, що відповідають його параметрам типу для конкретної реалізації себе що безпосередньо успадковує від цього. Якби цей клас був не абстрактним та інстанційним, або конкретна реалізація була знижена на два рівні, це не спрацювало (хоча трохи джимінгу може змусити його застосувати до будь-якої заздалегідь визначеної кількості рівнів понад один, або до найнижчого класу з параметрами загального типу X та ін.

У будь-якому випадку, на пояснення. Ось ще раз код, розділений на рядки для зручності посилання:

1 # Клас genericParameter0OfThisClass = 
2 # (клас)
3 # ((ParameterizedType)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArguments () [0];

Нехай «ми» - абстрактний клас із загальними типами, який містить цей код. Читаючи це приблизно зсередини:

  • Рядок 4 отримує поточний екземпляр класу конкретного класу. Це визначає конкретний тип нашого безпосереднього нащадка.
  • Рядок 5 отримує супертип типу класу як тип; це ми. Оскільки ми параметричного типу, ми можемо сміливо віднестись до ParameterizedType (рядок 3). Ключовим моментом є те, що коли Java визначає цей об'єкт Type, він використовує інформацію про тип, наявну у дочірніх даних, для асоціювання інформації типу з нашими параметрами типу в новому екземплярі ParameterizedType. Тож тепер ми можемо отримати доступ до конкретних типів для наших дженериків.
  • Рядок 6 отримує масив типів, відображених у нашому дженеріку, у порядку, який задекларований у коді класу. Для цього прикладу витягуємо перший параметр. Це повертається як тип.
  • Рядок 2 відкидає остаточний тип, повернений до класу. Це безпечно, тому що ми знаємо, які типи можуть приймати наші параметри загального типу і можемо підтвердити, що всі вони будуть класами (я не впевнений, як у Java можна було б отримати загальний параметр, у якого немає екземпляра Class власне, пов'язане з цим).

... і це майже все. Тож ми відштовхуємо інформацію про тип із власної конкретної реалізації назад до себе і використовуємо її для доступу до класу ручки. ми могли б подвоїти getGenericSuperclass () і пройти два рівні, або усунути getGenericSuperclass () і отримати значення для себе як конкретний тип (застереження: я не перевіряв ці сценарії, вони ще не підійшли для мене).

Це стає хитромудрим, якщо ваші конкретні діти відбирають довільну кількість хмелів, або якщо ви конкретні, а не остаточні, а особливо хитрі, якщо ви очікуєте, що хтось із ваших (мінливо глибоких) дітей матиме власні дженерики. Але зазвичай ти можеш розбиратися з цих міркувань, тож це дає тобі більшу частину шляху.

Сподіваюся, це комусь допомогло! Я визнаю, цей пост є давнім. Я, мабуть, оснащую це пояснення і зберігатиму його для інших питань.


3
Просто відповідаючи на ваше "Я не впевнений, як у Java можна було б отримати загальний параметр, який насправді не пов'язаний з ним екземпляром Class)": Це можливо, якщо параметр для загального класу є або " виразний вираз "(скажімо:? розширює SomeClass) або" змінна типу "(скажімо: <T>, де T походить із загального підкласу). в обох випадках повернені екземпляри "Тип" не можуть бути передані "Класу", оскільки кожен VM може мати різну реалізацію для обох, які реалізують інтерфейс Type, але не походять безпосередньо від java.lang.Class.
Роберто Андраде

19

Насправді я змусив це працювати. Розглянемо наступний фрагмент:

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


12
-1: це не вирішує задачу у питанні; він надає лише тип, декларований у підписі методу, а не власне тип виконання.
Майкл Боргвардт

5
Може не відповісти на питання ОП, але це корисна методика.
dnault

3
Це відповідь на зовсім інше питання.
Dawood ibn Kareem

Ви можете зателефонувати на m.getParameterTypes (), щоб отримати всі фактичні типи. Він поверне "Список" для прикладу.
Лі Меадор

Якщо genericParameterTypes [i] не є примірником ParameterizedType, це може бути клас. Це означає, що конкретний аргумент методу взагалі не параметризований.
Лі Медор

18

Насправді є рішення, застосувавши трюк "анонімного класу" та ідеї з маркерів Super Type :

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<?>. Вуаля!


Це опосередковано відповідає на питання: Початкова проблема не має рішення. Щоб тип зберігав свої загальні параметри, його потрібно вбудувати в інший тип, наприклад, підклас. Цей приклад показує, як потрібно змінити викликовий код, щоб можна було відновити параметр загального типу в chill ().
Флоріан Ф

FYI, перша посилання загинула
Ашвін Шарма

@AshvinSharma Я вважаю, що цей же матеріал доступний і тут: rgomes.info/using-typetokens-to-retrieve-generic-parameters
Yann-Gaël Guéhéneuc

7

Через стирання типу єдиним способом дізнатися тип списку було б передати тип як параметр методу:

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) {

    }

}

7

Додаток до відповіді @ 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
}

Про це не вимагали оригінальні публікації. Тут ви створили підклас Awesomeable<Beer>. У цьому випадку інформація про тип зберігається. Якщо перейти new Awesomable<Beer> ()до методу wt не буде працювати.
Флоріан F

@FlorianF Якщо ви переходите на Awesomable<Beer>льоту без явного визначення конкретного підкласу, як EstrellaGaliciaу цьому випадку, все-таки він виходить із параметру, що параметризується: я запустив його зараз: System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> Тип є: ParameterizedStuff $ Beer
Campa

6

Ні, це неможливо. Через зниження сумісності проблем, дженерики Java засновані на стиранні типу , тобто під час виконання, все, що у вас є, є негенеричним Listоб'єктом. Існує деяка інформація про параметри типу під час виконання, але вона розміщується у визначеннях класу (тобто ви можете запитати " який загальний тип використовує визначення цього поля? "), А не в об'єктах.


Хоча Java може зберігати це як метадані під час виконання, чи не могла? Я сподівався, що так і буде. Невдача.
cimnine

1
@ user99475: Ні. Я маю рацію, а Андрій помиляється. Він має на увазі інформацію про тип, про яку я згадую у другій частині своєї відповіді, але це не те, про що задається питанням.
Майкл Боргвардт

5

Як вказував @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 .



1

Ви можете отримати тип загального параметра з відображенням, як у цьому прикладі, який я знайшов тут :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}

як би ви зробили те саме, щоб отримати V Home <K, V>?
Рафаель Санчес

1

Швидкий відповідь на запитання - ні, ви не можете, через стирання типу Java.

Більш довгою відповіддю буде те, що якщо ви створили такий список так:

new ArrayList<SpideMan>(){}

Тоді в цьому випадку родовий тип зберігається в родовому надкласі нового анонімного класу вище.

Не те, що я рекомендую робити це зі списками, але це реалізація слухача:

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

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

Ось зараз я це зробив.


0

Це неможливо, оскільки дженерики на Java розглядаються лише під час компіляції. Таким чином, дженерики Java - це лише якийсь попередній процесор. Однак ви можете отримати фактичний клас учасників списку.


Так, це я зараз роблю. Але тепер я хочу знати тип, навіть якщо список порожній. Але чотири хлопці не можуть помилитися. Дякую вам всім)!
cimnine

19
Це не правда. Хоча це складно, ви можете побачити в наступному дописі, що ParameterizedType дозволяє це зробити.
Edmondo1984

1
Погодьтеся, це можна зробити. Приклад від DerMike окреслює це (працював на мене).
мак

LOL на "чотири хлопці не можуть помилитися". Ця відповідь правильна.
Dawood ibn Kareem

0

Я зашифрував це для методів, які очікують прийняти чи повернути Iterable<?...>. Ось код:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}

0

Ви не можете отримати загальний параметр зі змінної. Але ви можете з методу або поля оголошення:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();

Це дає мені виняток у темі "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Martinez

'params' - це 'Type', який має кілька підінтерфейсів. TypeVariableImpl - це той, який використовується для аргументів методу, і він вказує назву загального всередині <> s, а не Клас, який він представляє.
Лі Медор

0

Лише для мене читати цей фрагмент коду було важко, я просто розділив його на 2 читабельні рядки:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

Я хотів створити екземпляр параметра Type, не маючи жодних параметрів для мого методу:

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}

0

Ось ще одна хитрість. Використовуйте загальний масив vararg

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

0

Я помітив, що багато людей схиляються до getGenericSuperclass()рішення:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

Однак це рішення схильне до помилок. Це не спрацює належним чином, якщо у нащадків є дженерики. Врахуйте це:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

Який тип буде Bar.persistentClassмати? Class<Integer>? Ні, так буде Class<Double>. Це буде відбуватися з - за getClass()завжди повертається самий верхній клас, який Barв даному випадку, і його загальний клас супер це Foo<Double>. Отже, тип аргументу буде Double.

Якщо вам потрібно надійне рішення, яке не провалюється, я можу запропонувати два.

  1. Використовуйте Guava. Він має клас , який був зроблений саме для цієї мети: com.google.common.reflect.TypeToken. Він обробляє всі кутові корпуси просто чудово і пропонує ще кілька приємних функціональних можливостей. Мінус - додаткова залежність. Враховуючи, що ви використовували цей клас, ваш код буде виглядати просто і зрозуміло, як ось це:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Використовуйте спеціальний метод нижче. Він реалізує значно спрощену логіку, подібну до класу Guava, згаданого вище. Однак я не гарантую, що це схильність до помилок. Це все ж вирішує проблему з родовими нащадками.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

-1

Використання:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

Це неправильно. toArray()повертається Object[]. Ви не можете і не повинні залежати від того, що це якийсь конкретний підтип.
Radiodef
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.