Reputation: 2402
I have the following method with generics that executes the getter of each item in the list it receives:
public static <T, S> List<S> getValues(List<T> list, String fieldName) {
List<S> ret = new ArrayList<S>();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1, fieldName.length());
try {
if (list != null && !list.isEmpty()) {
for (T t : list) {
ret.add((S) t.getClass().getMethod(methodName).invoke(t));
}
}
} catch (IllegalArgumentException e) {
} catch (SecurityException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
} catch (NoSuchMethodException e) {
}
return ret;
}
It works perfectly fine if I call it as this:
List<Integer> ids = getValues(List<MyDTO>, "id");
request.setListIds(ids);
But, it gives me a compile error if I do it in a single line:
request.setListIds(getValues(List<MyDTO>, "id"));
The error says:
The method setListIds(List-Integer-) in the type MyDTO is not applicable for the arguments (List-Object-)
So, when I try to directly set the list, it is casting the generic to Object instead of Integer. Why is that?
Upvotes: 6
Views: 177
Reputation: 200166
It is due to Java's quite weak type inference. It can infer the type when you directly assign to a variable, but it won't infer by the target argument type, which you need in the second example.
You can overcome this with this.<Integer>getValues...
Upvotes: 8
Reputation: 11
Change your method signature like:
public static <T, S> List<S> getValues(List<T> list, String fieldName, Class<S> fieldType) {
//Your code goes here
}
Then following code will work seamlessly:
request.setListIds(getValues(List<MyDTO>, "id", Integer.class));
Upvotes: 0
Reputation: 5973
No cast took place in the actual compiled bytecode of the method, because of type erasure.
When generating bytecode, the compiler treats any variable of a parameter type as having the same type as that parameter type's upper bound, or Object
if the type is unbounded. So if S
in your method had the constraint <S extends Integer>
, the compiler would have inserted a cast to Integer
. However, as S
is unbounded, any references to S
are treated in the bytecode as being of type Object
- thus, no cast.
With your method as written, you could get rid of the compile error by filling in the type parameters when invoking the method:
YourClass.<MyDTO, Integer>getValues(list, "id")
Though as this looks clunky, you'd probably do well to get rid of the type parameter T
.
Upvotes: 1
Reputation: 8163
Apparently the compiler is not correctly guessing the generic type variables. In the assigment, he correctly guesses S=Integer
, while when passing the result as parameter, it is not taking into account the generic type of the method parameter.
This is because of type-erasure, since the signature of the method at runtime is setListIds(List)
, not setListIds(List<Integer>)
. By the way the same question was asked here, and the answer explains why the compiler behaves like that.
Upvotes: 1