【问题标题】:Get generic type of java.util.List获取 java.util.List 的泛型类型
【发布时间】:2010-12-28 21:31:32
【问题描述】:

我有;

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

有没有(简单的)方法来检索列表的泛型类型?

【问题讨论】:

  • 能够以编程方式检查 List 对象并查看其泛型类型。一个方法可能希望根据集合的泛型类型插入对象。这在运行时而不是编译时实现泛型的语言中是可能的。
  • 对——关于允许运行时检测的唯一方法是子类化:您实际上可以扩展泛型类型,然后使用反射查找子类型使用的类型声明。这是相当多的反思,但可能。不幸的是,没有简单的方法来强制必须使用泛型子类。
  • 肯定 stringList 包含字符串和 integerList 整数?为什么要让它变得更复杂?

标签: java generics


【解决方案1】:

如果这些实际上是某个类的字段,那么您可以通过一点反射来获得它们:

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

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

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

【讨论】:

  • 在某些情况下这肯定是有用的。例如在无配置的 ORM 框架中。
  • ..可以使用Class#getDeclaredFields()获取所有字段,无需知道字段名。
  • BalusC:对我来说,这听起来像是一个注入框架。无论如何,这就是我的意思。
  • @Oleg 您的变量使用&lt;?&gt;(通配符类型)而不是&lt;Class&gt;(类类型)。将您的&lt;?&gt; 替换为&lt;Class&gt; 或任何&lt;E&gt;
  • 即使在 10 年后仍然有用
【解决方案2】:

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

Method method = someClass.getDeclaredMethod("someMethod");
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

【讨论】:

    【解决方案3】:

    简短回答:不。

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

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

    您可以了解该类的一些信息,它可能被参数化的类型,但通常这只是扩展“对象”的任何东西,即任何东西。如果您将类型定义为

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

    AClass.class 将只包含参数 A 受 MyClass 限制的事实,但除此之外,没有办法分辨。

    【讨论】:

    • 如果没有指定泛型的具体类则为真;在他的示例中,他明确将列表声明为List&lt;Integer&gt;List&lt;String&gt;;在这些情况下,类型擦除不适用。
    【解决方案4】:

    集合的泛型类型只有在它实际上包含对象时才有意义,对吧?那么这样做不是更容易吗:

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

    【讨论】:

    • 如果你有一个空集合?或者,如果您有一个带有 Integer 和 String 的 Collection
    • 类的约定可以只要求传入最终对象的列表,并且如果列表为 null、空或列表类型无效,则方法调用将返回 null。跨度>
    • 在空集合的情况下,在运行时分配给任何列表类型都是安全的,因为其中没有任何内容,并且在发生类型擦除后,List is a List is a List。因此,我想不出空集合的类型会很重要的任何实例。
    • 好吧,如果您在线程中保留引用并在对象开始出现在生产者消费者场景中时对其进行处理,那么这“可能很重要”。如果列表中有错误的对象类型,可能会发生坏事。我想是为了防止您必须通过休眠来停止处理,直到列表中至少有一项才能继续。
    • 如果您有这种生产者/消费者场景,那么在不确定可以放入列表中的内容的情况下计划列表具有某种通用类型,无论如何都是糟糕的设计。相反,您将其声明为 Objects 的集合,当您将它们从列表中拉出时,您会根据它们的类型将它们提供给适当的处理程序。
    【解决方案5】:

    查找一个字段的通用类型:

    ((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
    

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        扩展 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 的回答相同的问题:如果列表为空怎么办?如果列表是包含字符串和整数的 类型怎么办?您的代码意味着如果列表中的第一项是字符串,并且根本没有整数,则只能添加更多字符串...
        • 如果列表是空的,那你就不走运了。如果列表是 Object 并且它实际上包含一个 Object 你没问题,但如果它有混合的东西你就不走运了。擦除意味着您可以做到这一点。在我看来,擦除是缺乏的。它的后果立即被人们所知甚少,不足以解决典型关注的有趣和期望的用例。没有什么可以阻止创建一个可以询问它需要什么类型的类,但这根本不是内置类的工作方式。集合应该知道它们包含什么,但是唉......
        【解决方案8】:

        在运行时,不,你不能。

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

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

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

        【讨论】:

        • 它是否适用于方法接收的参数而不是类的字段?
        • @opensas 可以使用method.getGenericParameterTypes() 获取方法参数的声明类型。
        • 这一直有效,直到您拥有class Foo&lt;T&gt; { public T bar; } Foo&lt;Integer&gt; fooField; - 在这种情况下,fooField 的“bar”成员返回的内容将是 TypeVariable 而不是 Class。即使您和我都可以清楚地看到 T 将是 Integer,但出于某种原因,我们只能使用子类的 getGenericSuperclass() 方法从 Foo 的子类中反射性地检索该信息(然后它将像上面的示例一样返回 ParametrizedType)。
        【解决方案9】:

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

        这涉及使用未经检查的强制转换,因此只有在您知道它是一个列表以及它可以是什么类型时才这样做。

        【讨论】:

          【解决方案10】:

          通常不可能,因为List&lt;String&gt;List&lt;Integer&gt; 共享同一个运行时类。

          不过,您可能能够反映保存列表的字段的声明类型(如果声明的类型本身并不引用您不知道其值的类型参数)。

          【讨论】:

            【解决方案11】:
            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是什么
            【解决方案12】:

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

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

            我不提倡这种方法,但在绑定中它可能有用。

            【讨论】:

            • 如果没有指定泛型的具体类则为真;在他的示例中,他明确将列表声明为List&lt;Integer&gt;List&lt;String&gt;;在这些情况下,类型擦除不适用。
            【解决方案13】:

            一个小小的帮助方法:

            /**
             * Get type of collection field.
             *
             * @param aClass A class containing collection.
             * @param collectionName A collection field name.
             */
            @SneakyThrows
            public static Class<?> getCollectionType(Class<?> aClass, String collectionName) {
              Field field = aClass.getDeclaredField(collectionName);
              ParameterizedType genericType = (ParameterizedType) field.getGenericType();
              return (Class<?>) genericType.getActualTypeArguments()[0];
            }
            

            【讨论】:

              【解决方案14】:

              使用反射获取Field,然后你可以这样做:field.genericType 来获取包含泛型信息的类型。

              【讨论】:

              • 考虑添加一些示例代码,否则这更适合评论而不是答案
              猜你喜欢
              • 2018-04-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-01-20
              相关资源
              最近更新 更多