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. 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", i.e. 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.
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. 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());
}
For finding generic type of one field:
((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:
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
Expanding on Steve K's answer:
/**
* 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);
}
}
Had the same problem, but I used instanceof instead. 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.
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. 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
).
Generally impossible, because List<String>
and List<Integer>
share the same runtime class.
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 ). 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.
What this code does is convert a generic type into a regular Java type. In Java, regular types can be detected with instanceof
.
Use Reflection to get the Field
for these then you can just do: field.genericType
to get the type that contains the information about generic as well.
精彩评论