У меня есть;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Есть ли (простой) способ получить общий тип списка?
У меня есть;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Есть ли (простой) способ получить общий тип списка?
Ответы:
Если это на самом деле поля определенного класса, то вы можете получить их с небольшой помощью отражения:
package test;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
public class Test {
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
public static void main(String... args) throws Exception {
Field stringListField = Test.class.getDeclaredField("stringList");
ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
System.out.println(stringListClass); // class java.lang.String.
Field integerListField = Test.class.getDeclaredField("integerList");
ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
System.out.println(integerListClass); // class java.lang.Integer.
}
}
Вы также можете сделать это для типов параметров и типов возвращаемых методов.
Но если они находятся в той же области действия класса / метода, где вы должны знать о них, то нет смысла их знать, потому что вы уже объявили их сами.
<?>
(подстановочный тип) вместо <Class>
(тип класса). Заменить <?>
ВУ <Class>
или любой другой <E>
.
Вы можете сделать то же самое для параметров метода:
Type[] types = method.getGenericParameterTypes();
//Now assuming that the first parameter to the method is of type List<Integer>
ParameterizedType pType = (ParameterizedType) types[0];
Class<?> clazz = (Class<?>) pType.getActualTypeArguments()[0];
System.out.println(clazz); //prints out java.lang.Integer
Краткий ответ: нет.
Это, вероятно, дубликат, не могу найти подходящий прямо сейчас.
Java использует так называемое стирание типов, что означает, что во время выполнения оба объекта эквивалентны. Компилятор знает, что списки содержат целые числа или строки, и поэтому могут поддерживать безопасную среду типов. Эта информация теряется (на основе экземпляра объекта) во время выполнения, и список содержит только «Объекты».
Вы МОЖЕТЕ узнать немного о классе, какими типами он может быть параметризован, но обычно это просто все, что расширяет «Объект», то есть что угодно. Если вы определяете тип как
class <A extends MyClass> AClass {....}
AClass.class будет содержать только тот факт, что параметр A ограничен MyClass, но более того, невозможно сказать.
List<Integer>
и List<String>
; в этих случаях стирание типа не применяется.
Универсальный тип коллекции должен иметь значение только в том случае, если в ней есть объекты, верно? Так не проще ли просто сделать:
Collection<?> myCollection = getUnknownCollectionFromSomewhere();
Class genericClass = null;
Iterator it = myCollection.iterator();
if (it.hasNext()){
genericClass = it.next().getClass();
}
if (genericClass != null) { //do whatever we needed to know the type for
Универсального типа во время выполнения не существует, но объекты внутри во время выполнения гарантированно совпадают с типом объявленного универсального, поэтому достаточно просто проверить класс элемента перед его обработкой.
Еще одна вещь, которую вы можете сделать, - это просто обработать список, чтобы получить членов правильного типа, игнорируя других (или обрабатывая их по-другому).
Map<Class<?>, List<Object>> classObjectMap = myCollection.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(Object::getClass));
// Process the list of the correct class, and/or handle objects of incorrect
// class (throw exceptions, etc). You may need to group subclasses by
// filtering the keys. For instance:
List<Number> numbers = classObjectMap.entrySet().stream()
.filter(e->Number.class.isAssignableFrom(e.getKey()))
.flatMap(e->e.getValue().stream())
.map(Number.class::cast)
.collect(Collectors.toList());
Это даст вам список всех элементов, чьи классы были подклассами, Number
которые вы можете затем обрабатывать по мере необходимости. Остальные элементы были отфильтрованы в другие списки. Поскольку они находятся на карте, вы можете обрабатывать их по своему усмотрению или игнорировать их.
Если вы хотите полностью игнорировать элементы других классов, это становится намного проще:
List<Number> numbers = myCollection.stream()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.collect(Collectors.toList());
Вы даже можете создать служебный метод, чтобы гарантировать, что список содержит ТОЛЬКО те элементы, которые соответствуют определенному классу:
public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
return input.stream()
.filter(cls::isInstance)
.map(cls::cast)
.collect(Collectors.toList());
}
Object
s, а когда вы вытаскиваете их из списка, вы передаете их соответствующему обработчику в зависимости от их типа.
Для нахождения универсального типа одного поля:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Если вам нужно получить универсальный тип возвращаемого типа, я использовал этот подход, когда мне нужно было найти методы в классе, который возвратил a, Collection
и затем получить доступ к их универсальным типам:
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
public class Test {
public List<String> test() {
return null;
}
public static void main(String[] args) throws Exception {
for (Method method : Test.class.getMethods()) {
Class returnClass = method.getReturnType();
if (Collection.class.isAssignableFrom(returnClass)) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) returnType;
Type[] argTypes = paramType.getActualTypeArguments();
if (argTypes.length > 0) {
System.out.println("Generic type is " + argTypes[0]);
}
}
}
}
}
}
Это выводит:
Общий тип это класс java.lang.String
Расширяя ответ Стива К:
/**
* Performs a forced cast.
* Returns null if the collection type does not match the items in the list.
* @param data The list to cast.
* @param listType The type of list to cast to.
*/
static <T> List<? super T> castListSafe(List<?> data, Class<T> listType){
List<T> retval = null;
//This test could be skipped if you trust the callers, but it wouldn't be safe then.
if(data!=null && !data.isEmpty() && listType.isInstance(data.iterator().next().getClass())) {
@SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.
List<T> foo = (List<T>)data;
return retval;
}
return retval;
}
Usage:
protected WhateverClass add(List<?> data) {//For fluant useage
if(data==null) || data.isEmpty(){
throw new IllegalArgumentException("add() " + data==null?"null":"empty"
+ " collection");
}
Class<?> colType = data.iterator().next().getClass();//Something
aMethod(castListSafe(data, colType));
}
aMethod(List<Foo> foo){
for(Foo foo: List){
System.out.println(Foo);
}
}
aMethod(List<Bar> bar){
for(Bar bar: List){
System.out.println(Bar);
}
}
Во время выполнения нет, вы не можете.
Однако с помощью отражения параметры типа являются доступными. Пытаться
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
Метод getGenericType()
возвращает объект Type. В этом случае это будет экземпляр класса ParametrizedType
, который, в свою очередь, имеет методы getRawType()
(которые List.class
в этом случае будут содержать ) и getActualTypeArguments()
который будет возвращать массив (в данном случае, длины один, содержащий либо String.class
или Integer.class
).
method.getGenericParameterTypes()
для получения объявленных типов параметров метода.
У меня была та же проблема, но я использовал instanceof. Сделал это так:
List<Object> listCheck = (List<Object>)(Object) stringList;
if (!listCheck.isEmpty()) {
if (listCheck.get(0) instanceof String) {
System.out.println("List type is String");
}
if (listCheck.get(0) instanceof Integer) {
System.out.println("List type is Integer");
}
}
}
Это включает использование непроверенных приведений, поэтому делайте это только тогда, когда вы знаете, что это список, и какой тип он может быть.
Как правило, невозможно, потому что List<String>
и List<Integer>
совместно использовать один и тот же класс времени выполнения.
Возможно, вы сможете отразить объявленный тип поля, содержащего список (если объявленный тип сам по себе не ссылается на параметр типа, значение которого вы не знаете).
Как уже говорили другие, единственный правильный ответ - нет, тип был стерт.
Если список содержит ненулевое количество элементов, вы можете исследовать тип первого элемента (например, используя его метод getClass). Это не скажет вам универсальный тип списка, но было бы разумно предположить, что универсальный тип был неким суперклассом типов в списке.
Я бы не защищал такой подход, но в безвыходном положении он может быть полезен.
List<Integer>
и List<String>
; в этих случаях стирание типа не применяется.
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GenericTypeOfCollectionTest {
public class FormBean {
}
public class MyClazz {
private List<FormBean> list = new ArrayList<FormBean>();
}
@Test
public void testName() throws Exception {
Field[] fields = MyClazz.class.getFields();
for (Field field : fields) {
//1. Check if field is of Collection Type
if (Collection.class.isAssignableFrom(field.getType())) {
//2. Get Generic type of your field
Class fieldGenericType = getFieldGenericType(field);
//3. Compare with <FromBean>
Assert.assertTrue("List<FormBean>",
FormBean.class.isAssignableFrom(fieldGenericType));
}
}
}
//Returns generic type of any field
public Class getFieldGenericType(Field field) {
if (ParameterizedType.class.isAssignableFrom(field.getGenericType().getClass())) {
ParameterizedType genericType =
(ParameterizedType) field.getGenericType();
return ((Class)
(genericType.getActualTypeArguments()[0])).getSuperclass();
}
//Returns dummy Boolean Class to compare with ValueObject & FormBean
return new Boolean(false).getClass();
}
}
getFieldGenericTypefieldGenericType
это не может быть найдено
Тип стирается, поэтому вы не сможете. См. Http://en.wikipedia.org/wiki/Type_erasure и http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure.
List<Integer>
и List<String>
; в этих случаях стирание типа не применяется.
Используйте Reflection, чтобы получить Field
их, тогда вы можете просто: field.genericType
получить тип, который также содержит информацию об общем.