Different type of return of the general method depending on the location of the call

I have the following method with generics that getster each element in the list that 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 fine if I call it the following:

 List<Integer> ids = getValues(List<MyDTO>, "id"); request.setListIds(ids); 

But this gives me a compilation error if I do this on one line:

 request.setListIds(getValues(List<MyDTO>, "id")); 

The error says:

The setListIds (List-Integer-) method in the MyDTO type is not applicable for arguments (List-Object -)

So, when I try to set the list directly, it returns a generic Object instead of Integer. Why is this?

+6
source share
4 answers

This is due to the fact that the Java language is rather weak. It can infer a type when you directly assign a variable, but it will not invoke the type of the target argument that you need in the second example.

You can overcome this with this.<Integer>getValues...

+8
source

Apparently, the compiler misunderstands common type variables. In assignment, he correctly guesses S=Integer , and when passing the result as a parameter, he does not take into account the general type of the method parameter.

This is due to erasing the styles since the method signature is at run time setListIds(List) , not setListIds(List<Integer>) . By the way, the same question was asked here , and the answer explains why the compiler behaves like this.

+1
source

In the actual compiled bytecode of the method, no custom was performed due to the type of erasure .

When generating bytecode, the compiler treats any variable of the parameter type as having the same type as the upper bound of the parameter type, or Object if the type is unlimited. Therefore, if S in your method has a <S extends Integer> constraint, the compiler would have to add cast to Integer . However, since S unlimited, any references to S processed in byte code as a type of type Object - thus, it is not executed.

Using your method, as written, you can get rid of a compilation error by filling in the type parameters when calling the method:

 YourClass.<MyDTO, Integer>getValues(list, "id") 

Although, since this looks awkward, you will probably be able to get rid of a parameter of type T

+1
source

Change your method signature, for example:

 public static <T, S> List<S> getValues(List<T> list, String fieldName, Class<S> fieldType) { //Your code goes here } 

Then the following code will work without problems:

 request.setListIds(getValues(List<MyDTO>, "id", Integer.class)); 
0
source

Source: https://habr.com/ru/post/957908/


All Articles