获取java.util.List的通用类型


262

我有;

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

有没有一种(简单的)方法来检索列表的通用类型?


为了能够以编程方式检查List对象并查看其通用类型。一种方法可能想要基于集合的通用类型插入对象。在运行时而不是编译时实现泛型的语言中,这是可能的。
郭富城2009年

4
正确-允许运行时检测的唯一方法是通过子类化:您可以实际扩展泛型类型,然后使用该子类型使用的反射查找类型声明。这是相当多的反思,但有可能。不幸的是,没有一种简单的方法可以强制使用泛型子类。
StaxMan 2010年

1
当然,stringList包含字符串和integerList整数吗?为什么要使其更复杂?
Ben Thurley 2014年

Answers:


408

如果这些实际上是某个类的字段,那么您可以在反射的帮助下获得它们:

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

您还可以对参数类型和方法的返回类型执行此操作。

但是,如果它们属于您需要了解它们的类/方法的同一范围之内,那么就没有必要了解它们,因为您已经自己声明了它们。


17
当然,在某些情况下,这是非常有用的。例如,在无配置的ORM框架中。
BalusC

3
..可以使用Class#getDeclaredFields()获取所有字段,而无需知道字段名称。
BalusC,2009年

1
BalusC:对我来说,这听起来像是一个注入框架。.无论如何,这就是我的意思。
falstro

1
@loolooyyyy我建议不要使用Guava的TypeToken,而不要使用TypeLiteral。github.com/google/guava/wiki/ReflectionExplained
Babyburger's

1
@Oleg您的变量正在使用<?>(通配符类型)而不是<Class>(类类型)。替换<?><Class>或以其他方式替换<E>
BalusC

19

您也可以对方法参数执行相同的操作:

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

在method.getGenericParameterTypes();中 什么是方法?
Neo Ravi

18

简短的回答:不。

这可能是重复项,目前找不到合适的副本。

Java使用一种称为类型擦除的类型,这意味着在运行时两个对象是等效的。编译器知道列表包含整数或字符串,因此可以维护类型安全的环境。该信息在运行时会丢失(基于对象实例),并且列表仅包含“对象”。

您可以了解有关该类的一些信息,以及可以对它进行参数化的类型,但通常这只是扩展“对象”的任何内容,即任何内容。如果您将类型定义为

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

AClass.class将仅包含参数A受MyClass限制的事实,但除此之外,还没有办法知道。


1
如果未指定泛型的具体类,那将是正确的。在他的示例中,他明确地将列表声明为List<Integer>List<String>;在这种情况下,类型擦除不适用。
Haroldo_OK

13

集合的泛型类型仅在实际上包含对象时才重要,对吗?因此,这样做不是更容易吗?

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

5
如果您有一个空的收藏集?或者,如果您有一个带有整数和字符串的Collection <Object>?
Falci

该类的协定可能只需要传入最终对象的列表,并且如果列表为null,为空或列表类型无效,则方法调用将返回null。
ggb667

1
对于空集合,可以安全地在运行时将其分配给任何列表类型,因为其中没有任何内容,并且在发生类型擦除后,“列表即列表”即为列表。因此,我无法想到空集合的类型很重要的任何实例。
Steve K

好吧,如果您在线程中保留引用并在生产者使用者场景中开始显示对象后进行处理,则“可能很重要”。如果列表的对象类型错误,则可能会发生不良情况。我猜想是为了防止您必须暂停处理,直到列表中至少有一项为止,然后再继续。
ggb667 '16

如果您使用的是这种生产者/消费者方案,则无论如何都要计划列表具有某种通用类型,而又不能确保可以在列表中放入什么是不好的设计。相反,您可以将其声明为的集合,Object并将它们从列表中拉出时,可以根据它们的类型将它们提供给适当的处理程序。
史蒂夫·K


10

如果需要获取返回类型的泛型类型,当我需要在返回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


6

扩展Steve K的答案:

/** 
* 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);
   }
}

与史蒂夫·K(Steve K)的回答同样的问题:如果列表为空怎么办?如果列表的类型为<Object>,其中包含一个字符串和一个整数,该怎么办?您的代码意味着你只能增加更多的字符串,如果在列表中的第一个项目是一个字符串,并且根本没有integeres ...
subrunner

如果列表为空,则说明您不走运。如果列表是Object并且它实际上包含一个Object,那么您可以,但是如果它包含多种事物,那么您很不走运。擦除意味着这是您所能做到的。我认为缺少擦除。它的后果一时才被人们理解,并且不足以解决典型关注的有趣和期望的用例。没有什么可以阻止创建一个可以询问其采用哪种类型的类的方法,但这根本不是内置类的工作方式。收藏应该知道它们包含的内容,但是a ...
ggb667

4

在运行时,不可以。

但是,通过反射可以访问类型参数。尝试

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

该方法getGenericType()返回一个Type对象。在这种情况下,它将是的实例ParametrizedType,而该实例又具有方法getRawType()List.class在这种情况下将包含)和和方法getActualTypeArguments(),该方法将返回一个数组(在这种情况下,长度为1,包含String.classInteger.class)。


2
并适用于方法接收的参数而不是类的字段吗???
opensas

@opensas可以使用 method.getGenericParameterTypes()用来获取方法参数的声明类型。
Radiodef

4

有同样的问题,但是我改用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");
       }
    }
}

这涉及到使用未经检查的强制类型转换,因此仅当您知道列表和类型时,才执行此操作。


3

通常是不可能的,因为List<String>List<Integer>共享相同的运行时类。

但是,您也许可以考虑保存列表的字段的声明类型(如果声明的类型本身并不引用您不知道其值的类型参数)。


2

正如其他人所说,唯一正确的答案是“否”,该类型已被删除。

如果列表中的元素数量非零,则可以调查第一个元素的类型(例如,使用getClass方法)。这不会告诉您列表的泛型类型,但是可以合理地假设泛型类型是列表中类型的某些超类。

我不会提倡这种方法,但是在某种程度上它可能是有用的。


如果未指定泛型的具体类,那将是正确的。在他的示例中,他明确地将列表声明为List<Integer>List<String>;在这种情况下,类型擦除不适用。
Haroldo_OK

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

1
getFieldGenericTypefieldGenericType找不到什么
-shareef


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.