Получить универсальный тип java.util.List - PullRequest
234 голосов
/ 22 декабря 2009

у меня есть;

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

Есть ли (простой) способ получения универсального типа списка?

Ответы [ 14 ]

375 голосов
/ 22 декабря 2009

Если это на самом деле поля определенного класса, то вы можете получить их с небольшой помощью отражения:

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.
    }
}

Вы также можете сделать это для типов параметров и типов возвращаемых методов.

Но если они находятся в той же области действия класса / метода, где вы должны знать о них, то нет смысла их знать, потому что вы уже объявили их сами.

18 голосов
/ 22 декабря 2009

Краткий ответ: нет.

Возможно, это дубликат, сейчас не могу найти подходящий.

Java использует так называемое стирание типов, что означает, что во время выполнения оба объекта эквивалентны. Компилятор знает, что списки содержат целые числа или строки, и поэтому могут поддерживать безопасную среду типов. Эта информация теряется (на основе экземпляра объекта) во время выполнения, и список содержит только «Объекты».

Вы МОЖЕТЕ немного узнать о классе, какими типами он может быть параметризован, но обычно это просто все, что расширяет "Объект", то есть что угодно. Если вы определите тип как

class <A extends MyClass> AClass {....}

AClass.class будет содержать только тот факт, что параметр A ограничен MyClass, но более того, невозможно сказать.

17 голосов
/ 27 февраля 2011

Вы можете сделать то же самое для параметров метода:

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
9 голосов
/ 18 ноября 2015

Для поиска универсального типа одного поля:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
9 голосов
/ 29 марта 2013

Если вам нужно получить универсальный тип возвращаемого типа, я использовал этот подход, когда мне нужно было найти методы в классе, который возвратил 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

8 голосов
/ 06 февраля 2013

Общий тип коллекции должен иметь значение только в том случае, если в нем действительно есть объекты, верно? Так не проще ли просто сделать:

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());
}
6 голосов
/ 13 сентября 2013

Расширяя ответ Стива К:

/** 
* 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);
   }
}
4 голосов
/ 10 августа 2017

У меня была такая же проблема, но я использовал 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");
       }
    }
}

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

4 голосов
/ 22 декабря 2009

Во время выполнения нет, вы не можете.

Однако через отражение доступны параметры типа . Попробуйте

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

Метод getGenericType() возвращает объект Type. В этом случае это будет экземпляр ParametrizedType, который, в свою очередь, имеет методы getRawType() (в данном случае он будет содержать List.class) и getActualTypeArguments(), который будет возвращать массив (в данном случае длина один, содержащая либо String.class, либо Integer.class).

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

Обычно невозможно, потому что List<String> и List<Integer> используют один и тот же класс времени выполнения.

Возможно, вы сможете отразить объявленный тип поля, содержащего список, хотя (если объявленный тип сам по себе не ссылается на параметр типа, значение которого вы не знаете).

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