Можно ли получить тип универсального параметра?
Пример:
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>());
}
}
java
generics
reflection
цимнин
источник
источник
Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
не знаю, в чем заключается ограничение.java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Я хочу попытаться разбить ответ @DerMike, чтобы объяснить:
Во-первых, стирание типа не означает, что JDK удаляет информацию о типе во время выполнения. Это метод, позволяющий сосуществовать проверке типов во время компиляции и совместимости типов во время выполнения на одном языке. Как следует из этого блока кода, JDK сохраняет стертую информацию о типе - она просто не связана с проверенными приведениями и прочим.
Во-вторых, это предоставляет информацию об общем типе универсальному классу ровно на один уровень выше по иерархии от проверяемого конкретного типа - т. Е. Абстрактный родительский класс с параметрами универсального типа может найти конкретные типы, соответствующие параметрам его типа, для конкретной реализации самого себя. что напрямую от него наследуется. Если бы этот класс не был абстрактным и был создан, или конкретная реализация была бы на два уровня ниже, это не сработало бы (хотя немного подергивания могло бы заставить его применяться к любому заранее определенному количеству уровней выше одного или до самого низкого класса. с параметрами универсального типа X и так далее).
Во всяком случае, к объяснению. Вот снова код, разделенный на строки для удобства:
Пусть «нас» будет абстрактным классом с универсальными типами, который содержит этот код. Читая это примерно наизнанку:
... и это почти все. Таким образом, мы отправляем информацию о типе из нашей собственной конкретной реализации обратно в себя и используем ее для доступа к дескриптору класса. мы могли бы удвоить getGenericSuperclass () и перейти на два уровня или исключить getGenericSuperclass () и получить значения для себя как конкретного типа (предостережение: я не тестировал эти сценарии, они еще не подошли для меня).
Это становится сложно, если ваши конкретные дети находятся на произвольном расстоянии от вас, или если вы конкретны, а не окончательны, и особенно сложно, если вы ожидаете, что у любого из ваших (с разной глубиной) детей будут свои собственные дженерики. Но обычно вы можете проектировать, исходя из этих соображений, так что это поможет вам в большинстве случаев.
Надеюсь, это кому-то помогло! Я понимаю, что этот пост старый. Я, вероятно, уберу это объяснение и оставлю его для других вопросов.
источник
На самом деле я заставил это работать. Рассмотрим следующий фрагмент:
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
источник
На самом деле есть решение, применяя уловку «анонимного класса» и идеи из токенов супертипа :
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<?>
. Вуаля!источник
Из-за стирания типа единственный способ узнать тип списка - передать тип в качестве параметра методу:
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) { } }
источник
Приложение к ответу @ 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, работать не будет.Awesomable<Beer>
на лету без явного определения конкретного подкласса, какEstrellaGalicia
в этом случае, он все равно получит параметризованный тип: Я запустил его прямо сейчас:System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());
---> Тип: ParameterizedStuff $ BeerНет, это невозможно. Из-за проблем совместимости вниз универсальные шаблоны Java основаны на стирании типа, например , во время выполнения, все, что у вас есть, - это неуниверсальный
List
объект. Существует некоторая информация о параметрах типа во время выполнения, но она находится в определениях классов (т. Е. Вы можете спросить, « какой общий тип используется в определении этого поля? »), А не в экземплярах объектов.источник
Как указывает @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 .
источник
Нет, это невозможно.
Вы можете получить общий тип поля, учитывая, что класс является единственным исключением из этого правила, и даже это немного взломать.
См. Пример этого в разделе « Знание общего типа в Java» .
источник
Вы можете получить тип универсального параметра с отражением, как в этом примере, который я нашел здесь :
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()); } }
источник
Быстрый ответ на вопрос : нет, вы не можете из-за стирания универсального типа Java.
Более длинный ответ был бы таким, если бы вы создали свой список следующим образом:
new ArrayList<SpideMan>(){}
Тогда в этом случае общий тип сохраняется в универсальном суперклассе нового анонимного класса выше.
Не то чтобы я рекомендовал делать это со списками, но это реализация слушателя:
new Listener<Type>() { public void doSomething(Type t){...}}
И поскольку экстраполяция общих типов суперклассов и суперинтерфейсов меняется между JVM, общее решение не так прямолинейно, как можно было бы предположить в некоторых ответах.
Вот теперь я это сделал.
источник
Это невозможно, поскольку в Java универсальные шаблоны рассматриваются только во время компиляции. Таким образом, дженерики Java - это всего лишь своего рода препроцессор. Однако вы можете получить фактический класс членов списка.
источник
Я закодировал это для методов, которые ожидают принятия или возврата
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()); }
источник
Вы не можете получить универсальный параметр из переменной. Но вы можете из объявления метода или поля:
Method method = getClass().getDeclaredMethod("chill", List.class); Type[] params = method.getGenericParameterTypes(); ParameterizedType firstParam = (ParameterizedType) params[0]; Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();
источник
Мне было сложно читать этот фрагмент кода, я просто разделил его на 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; }
источник
Вот еще одна хитрость. Используйте общий массив 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<> (); } }
источник
Я заметил, что многие склоняются к
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
.Если вам нужно надежное и надежное решение, я могу предложить два.
Guava
. Он имеет класс , который был сделан именно для этой цели:com.google.common.reflect.TypeToken
. Он отлично справляется со всеми угловыми случаями и предлагает более приятную функциональность. Обратной стороной является дополнительная зависимость. Если вы использовали этот класс, ваш код выглядел бы просто и понятно, например:class RootGeneric<T> { @SuppressWarnings("unchecked") public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType()); }
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; } }
источник
Использование:
источник
toArray()
возвращаетсяObject[]
. Вы не можете и не должны зависеть от конкретного подтипа.