0

I need to get the type of the item of a collection. For retrieving the class of a single instance I use the following code:

classUnderTest.getName()

But, how can I retrieve the class of the items of the following collection?

Collection collection = (Collection) getCollection.invoke(instance1);
Miljen Mikic
  • 14,765
  • 8
  • 58
  • 66
cloudy_weather
  • 2,837
  • 12
  • 38
  • 63

6 Answers6

0

Due to generic erasure, the Collection object does not know what type the items must be, only what they are right now. Depending on your needs, one solution might be to just do it the hard way and find the most specific superclass (or superinterface) of all the items in the collection.

However, do you really need to know the type of the collection items?

Tassos Bassoukos
  • 16,017
  • 2
  • 36
  • 40
  • yes, I need, because I am creating some test methods for different classes which are not linked with inheritance. – cloudy_weather Mar 05 '13 at 09:25
  • Then have a `Map` (or a different interface) with all the per-class test cases, and run through the various items' classes. Do you still need to find the items classes or is a simple item.getClass().getName() sufficient (for each item)? – Tassos Bassoukos Mar 05 '13 at 09:35
0

You cannot get/infer the generic type of the Collection at runtime due to Type Erasure. Generics are there for compile time safety, the type information is erased during runtime.

During runtime the instances are Object for collection and it does not care about the actual type on runtime.

Narendra Pathai
  • 41,187
  • 18
  • 82
  • 120
0

The short answer is that you can't.

Generic type parameters are not recorded as part of instances (objects) of the type. (Read up on "type erasure" ...) And besides, your example is using raw types anyway.

You can pick some element of a (non-empty) collection and get the type of that, but it doesn't tell you anything about the types of the other elements. (They could be the same ... or different.) And if the collection is empty or contains null references you can't even reliably do that.

Anyway, this will give you the type of the first element of a collection.

    Collection c = ...
    Iterator it = c.iterator();
    if (c.hasNext()) {
        Class clazz = c.next().getClass();
        ...
    }

You could iterate over the entire collection and (with some difficulty) figure out the common class or interface that they all conform to. But even that is not necessarily the same as the declared element type.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
0

In general, you can't do that. The collection may be empty or composed only of null values for example. Also, what about interfaces? there may be multiple interfaces which are common to all of the collection elements.

However, maybe one of the following will be helpful in your case:

1) Analyzing the collection contents: Assuming the collection is non-empty, scan all items in the collection and find their lowest common ancestor in the class hierarchy.

2) Getting the generic declared type: If the object you are inspecting is declared as a data member (with generics) of some class, you can evaluate its declared generic type at runtime, using reflection (see Field.getGenericType())

-- EDIT --

Just for practicing, I implemented the lowest common ancestor utility:

    public static Class<?> getLCA(Collection<?> c) {
        Class<?> res = null;
        for (Object obj : c)
            if (obj != null) {              
                Class<?> clazz = obj.getClass();
                res = (res == null)? clazz : getLCA(res, clazz);
            }

        return res;
    }

    private static Class<?> getLCA(Class<?> classA, Class<?> classB) {
        if (classA.isAssignableFrom(classB))
            return classA;
        if (classB.isAssignableFrom(classA))
            return classB;

        while (!classA.isAssignableFrom(classB))
            classA = classA.getSuperclass();

        return classA;
    }

Efficiency: O(n*h), where n is the collection size and h is the maximum depth of the collection items in the class hierarchy.

Eyal Schneider
  • 22,166
  • 5
  • 47
  • 78
0

you can't. In Java there is no way of retrieving generic types at runtime. It is called Type Erasure.

but you could iterate over the Collection and get the most general type. But even if you do that you have no way of passing that type as a generic type around.

Your best bet would be to supress warnings and ensure that nothing illegal is passed into the methode.

Blank Chisui
  • 1,043
  • 10
  • 25
0

The answers is quite evident from the posts most people have said... that is You cannot get/infer the generic type of the Collection at runtime due to Type Erasure

But, I would like to demonstrate what it means

Suppose we create a class MyClass and do this

Collection<String> collection = new ArrayList<String>();

public static void main(String[] args)  {

    Field stringListField = null;
    try {
        stringListField = MyClass.class.getDeclaredField("collection");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); 

    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
}

It will print class java.lang.String as result... that means it is able to infer when Collection object is using proper generic arguments Collection

But, If we put,

Collection<?> collection = new ArrayList<String>();

Then it will compile just fine but on running you will get the following exception..

sun.reflect.generics.reflectiveObjects.WildcardTypeImpl cannot be cast to java.lang.Class

This is the exception thrown to runtime Type Erasure

AurA
  • 12,135
  • 7
  • 46
  • 63