Java reflection: What does my Collection contain?
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开发者_JS百科 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
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.
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());
}
}
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.
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.
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 ;)
精彩评论