312

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?

skaffman
  • 398,947
  • 96
  • 818
  • 769
Thizzer
  • 16,153
  • 28
  • 98
  • 139
  • To be able to programmaticly inspect a List object and see its generic type. A method may want to insert objects based on the generic type of the collection. This is possible in languages that implement generics at runtime instead of compile time. – Steve Kuo Dec 21 '09 at 22:49
  • 4
    Right -- about the only way to allow runtime detection is by sub-classing: you CAN actually extend generic type and then using reflection find type declaration that subtype used. This is quite a bit of reflection, but possible. Unfortunately there is no easy way to enforce that one must use generic sub-class. – StaxMan Sep 10 '10 at 20:41
  • 1
    Surely stringList contains strings and integerList integers? Why make it more complicated? – Ben Thurley Sep 16 '14 at 16:09

15 Answers15

476

If those are actually fields of a certain class, then you can get them with a little help of reflection:

package com.stackoverflow.q1942644;

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<>();
    List<Integer> integerList = new ArrayList<>();

    public static void main(String... args) throws Exception {
        Class<Test> testClass = Test.class;

        Field stringListField = testClass.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String

        Field integerListField = testClass.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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • while this is definitely an interesting exercise in generics, if you now what field you want, you usually know what type it is.. :) Determining the type of a parameter would've been a lot more useful I suppose. Good when making injection frameworks or similar though. +1 – falstro Dec 21 '09 at 21:25
  • 21
    There are certainly situations where this is above useful. In for example configurationless ORM frameworks. – BalusC Dec 21 '09 at 21:29
  • 3
    ..which can use Class#getDeclaredFields() to get all fields without the need to know the field name. – BalusC Dec 21 '09 at 21:31
  • 1
    BalusC: That sounds like an injection framework to me.. That's the kind of use I meant anyway. – falstro Dec 21 '09 at 21:34
  • Then why does Guice has a TypeLiteral? TypeLiteral is ugly. – hkoosha Jul 04 '15 at 09:45
  • does this also work with other types of Collections like Set or HashMap? – Hendra Anggrian May 12 '16 at 16:03
  • this works for Maps as well. the key is the cast to (ParameterizedType), without it there is no way to get the list of classes that make up the list or map – Dustin Oct 17 '16 at 16:09
  • 1
    @loolooyyyy Rather than use TypeLiteral, I recommend using TypeToken from Guava. https://github.com/google/guava/wiki/ReflectionExplained – Babyburger May 15 '17 at 13:58
  • @babyburger also let me invite TypeParameterMatcher to the party. you'll have to copy paste, if you aren't using netty in a project (has specific use cases but it rocks): https://netty.io/4.0/xref/io/netty/util/internal/TypeParameterMatcher.html – hkoosha May 15 '17 at 18:26
  • Does not work for me, getting `Caused by: java.lang.ClassCastException: class sun.reflect.generics.reflectiveObjects.WildcardTypeImpl cannot be cast to class java.lang.Class (sun.reflect.generics.reflectiveObjects.WildcardTypeImpl and java.lang.Class are in module java.base of loader 'bootstrap')`. Trying to resolve it for few hours already. – Oleg Kuts Jul 10 '19 at 16:32
  • 2
    @Oleg Your variable is using `>` (wildcard type) instead of `` (class type). Replace your `>` by `` or whatever ``. – BalusC Jul 11 '19 at 11:22
  • @BalusC Thanks for reply. did work for me( in my case), while throwing another exception `Caused by: java.lang.ClassCastException: class sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to class java.lang.Class (sun.reflect.generics.reflectiveObjects.TypeVariableImpl and java.lang.Class are in module java.base of loader 'bootstrap')`. I am ok with this, went another way, posting just FYI. – Oleg Kuts Jul 11 '19 at 12:09
  • 4
    still useful even after 10 years – JayD Feb 01 '20 at 11:02
29

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
Michael
  • 41,989
  • 11
  • 82
  • 128
tsaixingwei
  • 636
  • 9
  • 13
20

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.

falstro
  • 34,597
  • 9
  • 72
  • 86
  • 2
    That will be true if the concrete class of the generic is not specified; in his example, he's explicitly declaring the lists as `List` and `List`; in those cases, type erasure does not apply. – Haroldo_OK Mar 07 '18 at 13:25
18

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());
}
Steve K
  • 4,863
  • 2
  • 32
  • 41
  • 7
    And if you have a empty collection ? Or if you have a Collection with a Integer and a String ? – Falci May 14 '13 at 16:10
  • The contract for the class could require only lists of final objects be passed in, and that the method call will return null if the list is null, empty, or if the list type is invalid. – ggb667 Aug 08 '13 at 18:58
  • 1
    In the case of an empty collection, it is safe to assign to any list type at runtime, because there's nothing in it, and after type erasure occurs, a List is a List is a List. Because of this, I'm unable to think of any instance where the type of an empty collection will matter. – Steve K Feb 02 '15 at 04:10
  • Well it "could matter" if you held onto the reference in a thread and processed it once objects started showing up in a producer consumer scenario. If the list had the wrong object types bad things could happen. I guess to prevent that you would have to halt processing by sleeping till there was at least one item in the list before continuing. – ggb667 Nov 22 '16 at 15:44
  • If you have that kind of producer/consumer scenario, planning for the list to have a certain generic type without being sure what could be put in the list is bad design, anyway. Instead you'd declare it as a collection of `Object`s and when you pull them out of the list, you'd give them to an appropriate handler based on their type. – Steve K Nov 22 '16 at 23:04
  • Well yes, that would be intelligent. But it supposes you have control of the design, and one or both sides of the converstaion and aren't using a pluggable libarary for instance. – ggb667 Feb 05 '18 at 15:56
  • You may need to process things to get typed lists to make libraries compile together, but the method I described is really the only way to do that at runtime. Process the list as a list or stream of objects, and collect all the items in your object list/stream that test as that type into your new list/stream which you can then pass off to your library. And it only assumes that you have control of the design of the method you are actually writing. – Steve K Feb 06 '18 at 03:10
  • This solution works for me because I am rejecting input where my Collection> is empty. – wheeleruniverse Feb 16 '18 at 19:04
12

For finding generic type of one field:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Nik Kashi
  • 4,447
  • 3
  • 40
  • 63
11

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

Aram Kocharyan
  • 20,165
  • 11
  • 81
  • 96
8

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);
   }
}
ggb667
  • 1,881
  • 2
  • 20
  • 44
  • Same problems as with Steve K's answer: What if the list is empty? What if the list is of type that contains a String and an Integer? Your code means that you can only add more Strings if the first item in the list is a string, and no integeres at all... – subrunner Feb 05 '18 at 08:52
  • If the list is empty you are out of luck. If the list is Object and it actually contains an Object you are OK, but if it has a mix of things you are out of luck. Erasure means this is as good as you can do. Erasure is lacking in my opinion. The ramifications of it are at once poorly understood and insufficient to address the interesting and desired use cases of typical concern. Nothing prevents creating a class that can be asked what type it takes, but that simply isn't how the built in classes work. Collections ought to know what they contain, but alas... – ggb667 Feb 05 '18 at 15:53
5

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.

Andy
  • 2,469
  • 1
  • 25
  • 25
4

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

Scott Morrison
  • 3,100
  • 24
  • 39
  • 2
    and does it work for parameters received by a method instead of fields of a class??? – opensas Nov 26 '10 at 04:46
  • @opensas You can use [`method.getGenericParameterTypes()`](https://docs.oracle.com/javase/10/docs/api/java/lang/reflect/Method.html#getGenericParameterTypes%28%29) to get the declared types of a method's parameters. – Radiodef Aug 15 '18 at 23:57
  • This works right up until you have, let's say, `class Foo { public T bar; } Foo fooField;` - in this case, what is returned for fooField's 'bar' member will be a TypeVariable instead of a Class. Even though you and I can see clearly that T will be Integer, for some reason we can only reflexively retrieve that information from a subclass of Foo using the subclass's getGenericSuperclass() method (which will then return a ParametrizedType like the above example). – Falkreon Oct 22 '20 at 19:39
3

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

meriton
  • 68,356
  • 14
  • 108
  • 175
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();
    }
}
DSantiagoBC
  • 464
  • 2
  • 11
Ashish
  • 59
  • 1
1

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.

Chris Arguin
  • 11,850
  • 4
  • 34
  • 50
  • That will be true if the concrete class of the generic is not specified; in his example, he's explicitly declaring the lists as `List` and `List`; in those cases, type erasure does not apply. – Haroldo_OK Mar 07 '18 at 13:27
1

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];
}
Zon
  • 18,610
  • 7
  • 91
  • 99
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.

Blake McBride
  • 394
  • 3
  • 16
-1

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.