Generic return type upper bound - interface vs. class - surprisingly valid code
CharSequence
is an interface
. Therefore even if SomeClass
does not implement CharSequence
it would be perfectly possible to create a class
class SubClass extends SomeClass implements CharSequence
Therefore you can write
SomeClass c = getCharSequence();
because the inferred type X
is the intersection type SomeClass & CharSequence
.
This is a bit odd in the case of Integer
because Integer
is final, but final
doesn't play any role in these rules. For example you can write
<T extends Integer & CharSequence>
On the other hand, String
is not an interface
, so it would be impossible to extend SomeClass
to get a subtype of String
, because java does not support multiple-inheritance for classes.
With the List
example, you need to remember that generics are neither covariant nor contravariant. This means that if X
is a subtype of Y
, List<X>
is neither a subtype nor a supertype of List<Y>
. Since Integer
does not implement CharSequence
, you cannot use List<Integer>
in your doCharSequence
method.
You can, however get this to compile
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
If you have a method that returns a List<T>
like this:
static <T extends CharSequence> List<T> foo()
you can do
List<? extends Integer> list = foo();
Again, this is because the inferred type is Integer & CharSequence
and this is a subtype of Integer
.
Intersection types occur implicitly when you specify multiple bounds (e.g. <T extends SomeClass & CharSequence>
).
For further information, here is the part of the JLS where it explains how type bounds work. You can include multiple interfaces, e.g.
<T extends String & CharSequence & List & Comparator>
but only the first bound may be a non-interface.
The type that is inferred by your compiler prior to the assignment for X
is Integer & CharSequence
. This type feels weird, because Integer
is final, but it's a perfectly valid type in Java. It is then cast to Integer
, which is perfectly OK.
There is exactly one possible value for the Integer & CharSequence
type: null
. With the following implementation:
<X extends CharSequence> X getCharSequence() {
return null;
}
The following assignment will work:
Integer x = getCharSequence();
Because of this possible value, there's no reason why the assignment should be wrong, even if it is obviously useless. A warning would be useful.
The real problem is the API, not the call site
In fact, I've recently blogged about this API design anti pattern. You should (almost) never design a generic method to return arbitrary types because you can (almost) never guarantee that the inferred type will be delivered. An exception are methods like Collections.emptyList()
, in case of which the emptiness of the list (and generic type erasure) is the reason why any inference for <T>
will work:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}