Luis Sep
Luis Sep

Reputation: 2402

Different return type of generic method depending on invocation location

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

Answers (4)

Marko Topolnik
Marko Topolnik

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

Jigar Shah
Jigar Shah

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

pobrelkey
pobrelkey

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

kutschkem
kutschkem

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

Related Questions