简体   繁体   English

获取 java.util.List 的泛型类型

[英]Get generic type of java.util.List

I have;我有;

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

Is there a (easy) way to retrieve the generic type of the list?有没有(简单的)方法来检索列表的泛型类型?

If those are actually fields of a certain class, then you can get them with a little help of reflection:如果这些实际上是某个类的字段,那么您可以通过一点反思来获得它们:

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

You can also do that for parameter types and return type of methods.您也可以对参数类型和方法的返回类型执行此操作。

But if they're inside the same scope of the class/method where you need to know about them, then there's no point of knowing them, because you already have declared them yourself.但是,如果它们在您需要了解它们的类/方法的同一范围内,那么了解它们就没有意义,因为您已经自己声明了它们。

You can do the same for method parameters as well:您也可以对方法参数执行相同操作:

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

Short answer: no.简短的回答:没有。

This is probably a duplicate, can't find an appropriate one right now.这可能是重复的,现在找不到合适的。

Java uses something called type erasure, which means at runtime both objects are equivalent. Java 使用一种称为类型擦除的东西,这意味着在运行时两个对象是等价的。 The compiler knows the lists contain integers or strings, and as such can maintain a type safe environment.编译器知道列表包含整数或字符串,因此可以维护类型安全的环境。 This information is lost (on an object instance basis) at runtime, and the list only contain 'Objects'.此信息在运行时丢失(基于对象实例),并且列表仅包含“对象”。

You CAN find out a little about the class, what types it might be parametrized by, but normally this is just anything that extends "Object", ie anything.你可以找到一些关于类的信息,它可能被参数化的类型,但通常这只是扩展“对象”的任何东西,即任何东西。 If you define a type as如果您将类型定义为

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

AClass.class will only contain the fact that the parameter A is bounded by MyClass, but more than that, there's no way to tell. AClass.class 将仅包含参数 A 受 MyClass 限制的事实,但除此之外,无法分辨。

The generic type of a collection should only matter if it actually has objects in it, right?集合的泛型类型只有在它实际上包含对象时才有意义,对吧? So isn't it easier to just do:那么这样做不是更容易吗:

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

There's no such thing as a generic type in runtime, but the objects inside at runtime are guaranteed to be the same type as the declared generic, so it's easy enough just to test the item's class before we process it.运行时没有泛型类型,但运行时内部的对象保证与声明的泛型类型相同,因此只需在处理项目之前测试项目的类就很容易了。

Another thing you can do is simply process the list to get members that are the right type, ignoring others (or processing them differently).您可以做的另一件事是简单地处理列表以获取正确类型的成员,忽略其他成员(或以不同方式处理它们)。

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

This will give you a list of all items whose classes were subclasses of Number which you can then process as you need.这将为您提供其类是Number子类的所有项目的列表,然后您可以根据需要对其进行处理。 The rest of the items were filtered out into other lists.其余项目被过滤到其他列表中。 Because they're in the map, you can process them as desired, or ignore them.因为它们在地图中,所以您可以根据需要处理它们,或者忽略它们。

If you want to ignore items of other classes altogether, it becomes much simpler:如果您想完全忽略其他类的项目,它会变得更简单:

List<Number> numbers = myCollection.stream()
    .filter(Number.class::isInstance)
    .map(Number.class::cast)
    .collect(Collectors.toList());

You can even create a utility method to insure that a list contains ONLY those items matching a specific class:您甚至可以创建一个实用方法来确保列表仅包含与特定类匹配的那些项目:

public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
    return input.stream()
            .filter(cls::isInstance)
            .map(cls::cast)
            .collect(Collectors.toList());
}

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

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

If you need to get the generic type of a returned type, I used this approach when I needed to find methods in a class which returned a Collection and then access their generic types:如果您需要获取返回类型的泛型类型,当我需要在返回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]);
                    }
                }
            }
        }

    }

}

This outputs:这输出:

Generic type is class java.lang.String泛型类型是类 java.lang.String

Expanding on Steve K's answer:扩展史蒂夫 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;
        retval=foo;
    }
    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);
   }
}

At runtime, no, you can't.在运行时,不,你不能。

However via reflection the type parameters are accessible.但是,通过反射可以访问类型参数。 Try尝试

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

The method getGenericType() returns a Type object. getGenericType()方法返回一个 Type 对象。 In this case, it will be an instance of ParametrizedType , which in turn has methods getRawType() (which will contain List.class , in this case) and getActualTypeArguments() , which will return an array (in this case, of length one, containing either String.class or Integer.class ).在这种情况下,它将是ParametrizedType的一个实例,它又具有方法getRawType() (在这种情况下将包含List.class )和getActualTypeArguments() ,它将返回一个数组(在这种情况下,长度为 1 ,包含String.classInteger.class )。

Had the same problem, but I used instanceof instead.有同样的问题,但我用 instanceof 代替。 Did it this way:这样做的:

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

This involves using unchecked casts so only do this when you know it is a list, and what type it can be.这涉及使用未经检查的强制转换,因此只有在您知道它是一个列表以及它可以是什么类型时才这样做。

Generally impossible, because List<String> and List<Integer> share the same runtime class.通常是不可能的,因为List<String>List<Integer>共享同一个运行时类。

You might be able to reflect on the declared type of the field holding the list, though (if the declared type does not itself refer to a type parameter whose value you don't know).但是,您可能能够反映保存列表的字段的声明类型(如果声明的类型本身不引用您不知道其值的类型参数)。

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

As others have said, the only correct answer is no, the type has been erased.正如其他人所说,唯一正确的答案是否定的,类型已被删除。

If the list has a non-zero number of elements, you could investigate the type of the first element ( using it's getClass method, for instance ).如果列表的元素数量非零,您可以调查第一个元素的类型(例如,使用它的 getClass 方法)。 That won't tell you the generic type of the list, but it would be reasonable to assume that the generic type was some superclass of the types in the list.这不会告诉您列表的泛型类型,但可以合理地假设泛型类型是列表中类型的某个超类。

I wouldn't advocate the approach, but in a bind it might be useful.我不提倡这种方法,但在绑定时它可能有用。

A tiny helper method for that:一个小小的帮助方法:

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

I found an answer that works perfectly in my case.我找到了一个非常适合我的答案。

class ArrayListString extends ArrayList<String> {}
class ArrayListInteger extends ArrayList<Integer> {}

ArrayListString als = new ArrayListString();
ArrayListInteger ali = new ArrayListInteger();

void whatType(Object a) {
    if (a instanceof ArrayListString)
        System.out.println("ArrayListString");
    else if (a instanceof ArrayListInteger)
        System.out.println("ArrayListInteger");
}

whatType(als);
whatType(ali);

als and ali will work as their generics do. alsali将像他们的泛型一样工作。

What this code does is convert a generic type into a regular Java type.这段代码所做的是将泛型类型转换为常规 Java 类型。 In Java, regular types can be detected with instanceof .在 Java 中,可以使用instanceof检测常规类型。

I have;我有;

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

Is there a (easy) way to retrieve the generic type of the list?有没有一种(简单的)方法来检索列表的通用类型?

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM