views:

135

answers:

5

Hi guys, I've defined a method in a class:

public void setCollection(Collection<MyClass>);

and in another class

public void setCollection(Collection<OtherClass>);

(and really, lots of similar classes)

All are in classes with the same superclass, and I have a method in a support-class where I want to call this method and set it with items of the correct class type. Now, I can get that I'm setting a Collection by doing

Method setter = ...;
Class<?> paramClass = setter.getParameterTypes()[0]; // Is Collection in this case
if(paramClass.equals(Collection.class)) {
  HashSet col = new HashSet();
  // fill the set with something
  setter.invoke(this, col);
}

But how do I figure out what class the objects in this collection should belong to?

Cheers

Nik

+1  A: 

Java implements generics with type erasure. The compiler uses this type information only for safety checks, the byte code doesn't contain any information about generic types. Consequently, the runtime doesn't know about it either, so you cannot retrieve it by reflection.

You can find some additional information here.

Update:

To correct myself, consider this method:

public <T> String doSomething(T first, int second) {
    return first.toString();
}

In the byte code, this will have the following signature (note the T type):

<T:Ljava/lang/Object;>(TT;I)Ljava/lang/String

So what I said above is only true with this exception.

candiru
+6  A: 
Method.getGenericParameterTypes();

returns an array of Types which the parameter accepts. Complexity increases exponentially from there.

In your specific case, this would work:

    Method m = Something.class.getMethod("setCollection", Collection.class);
    Class<?> parameter = (Class<?>) ((ParameterizedType) m.getGenericParameterTypes()[0]).getActualTypeArguments()[0];

But there are a lot of potential gotchas there, depending on how the parameter was declared. If it is a simple as in your example, great. If not, then there are a bunch of types you have to account for, both in the getGenericParameterTypes() method and in the getActualTypeArguments() method. It gets very hairy and ugly very fast.

Yishai
Thanks a bunch, this solved my problem! Added complexity here will reduce my complexity overall, so I take my hat of for you and thank you sincerely. :-)
niklassaers
A: 

You seem to know the setter already. So it seems to me that, in this case, MyClass and OtherClass (not the collection, but the items) share something of a similar interface. In that case you don't really need to know the exact type.

extraneon
A: 

As candiru already explained, the 'Collection Type' is erased, at runtime it's nothing but a Collection of Object types.

If you're allowed to slightly change the message signature in your subclasses to

public void setCollection(Collection<T> aCollection, Class<T> elementType);

then you could pass a second parameter which holds the information of the 'Collection Type', usage would be like:

public void setCollection(Collection<MyClass> myClassCollection, 
                          Class<MyClass> clazz);

and to use it:

Collection<MyClass> myClassCollection = new ArrayList<MyClassCollection>();
setCollection(myClassCollection, MyClass.class);

But on the other hand - usually you don't have to care about it because the type is erased at runtime and you can put any kind of object in a paramterized collection, as long as you're not catched by the compiler ;)

Andreas_D
+1  A: 

I would consider moving the setCollection methods out into their own classes and then doing something more like this (you can change it to create the whole collection at once instead of doing it as an element at a time).

This gets rid of reflection and ensures that it it typesafe at compile time. I could be misunderstanding what it is you are trying to do, but I think I got it. The "init" method would be the helper method (at least if I understand what you are aimng for).

public class Main 
{
    public static void main(String[] args) 
    {
        List<Car> car;
        List<Bar> bar;

        car = new ArrayList<Car>();
        bar = new ArrayList<Bar>();
        init(car, new CarCreator());
        init(bar, new BarCreator());
    }

    private static <T> void init(final List<T>    list,
                                 final Creator<T> creator)
    { 
        for(int i = 0; i < 10; i++)
        {
            final T instance;

            instance = creator.newInstance(i);
            list.add(instance);
        }
    }
}

interface Foo
{    
}

class Bar implements Foo 
{ 
}

class Car implements Foo 
{ 
}

interface Creator<T>
{
    T newInstance(int i);
}

class BarCreator
    implements Creator<Bar>
{
    public Bar newInstance(final int i)
    {
        return (new Bar());
    }
}

class CarCreator
    implements Creator<Car>
{
    public Car newInstance(final int i)
    {
        return (new Car());
    }
}
TofuBeer