bladekp
bladekp

Reputation: 1647

Java generic methods, wildcard List return type

Java's 8 method signature looks as follows:

public static <E extends CharSequence> List<? super E> m(List<E> list);

And somewhere in code, method call:

result = m(list);

Suppose that I've create argument list as follows:

List<String> list = new ArrayList<>();

My question is, why the result can be just raw List, and can't be even a List<Object>?

Upvotes: 2

Views: 1885

Answers (2)

Seelenvirtuose
Seelenvirtuose

Reputation: 20618

Calling your method with a List<String> as an argument is like calling the following method:

public static List<? super String> m(List<String> list) { ... }

Consider the following starting code:

List<String> list = new ArrayList<>();
List<? super String> result = m(list);

This calls the above mentioned method and stores the return value into the variable result - which is typed exactly with the method's return type.

The question now is: To what variables - or better what types - can you assign this variable? So we are speaking about assignment compatibility.

Consider these assignments:

List<String> result1 = result; // compiler error: type mismatch (not assignable)
List<Object> result2 = result; // compiler error: type mismatch (not assignable)

List result3 = result; // ok
List<?> result4 = result; // ok

List<? super String> result5 = result; // ok
List<? extends Object> result6 = result; // ok

To understand the nature of this error, you must know that generics are invariant. That means, the type List<String> is not a subtype of List<Object> - although the types String and Object have such a subtype hierarchy.

So, what we are trying here is:

  1. Assign a List<? super String> to a List<String> => fails, no subtyping
  2. Assign a List<? super String> to a List<Object> => fails, no subtyping
  3. Assign a List<? super String> to a List => succeeds, because using raw types opts out from type checking generally
  4. Assign a List<? super String> to a List<?> => succeeds, because a List<?> is the supertype of all List<...>
  5. Assign a List<? super String> to a List<? super String> => succeeds, because ... well ...
  6. Assign a List<? super String> to a List<? ectends Object> => succeeds, because List<? ectends Object> is essentially the same as List<?>.

Note, that trying to assign a List<? super String> to a List<? super CharSequence> or a List<? extends CharSequence> will also fail. They are not in a subtype hierarchy.

Why is that so? The compiler cannot guarantee that the actual list was instantiated with a type that matches the constraint ? super/extends CharSequence.

Upvotes: 3

Hoopje
Hoopje

Reputation: 12932

The reason you can't do that is because Java generics are not covariant. That is, List<Object> is not a super-class of List<String>. The method signature of your method m would however allow returning a List<String>, and then you would have a List<Object> typed reference pointing to List<String>. Since generic type information is not available at run time, this would not raise an exception immediately, but it could result in type errors in unrelated places.

For example, suppose m is implemented as follows:

private static List<CharSequence> theResult = new ArrayList<>();

public static <E extends CharSequence> List<? super E> m(List<E> list) {
    theResult.addAll(list);
    return theResult;
}

Then using your method as follows would ad a non-CharSequence object to a List<CharSequence>:

List<Object> os = m(someList);
os.add(new Object());

Luckily, the above does not compile.

Upvotes: 1

Related Questions