This is a question of when parameterized types are "well-formed", i.e. what type arguments are allowed. The JLS isn't very well written on this topic, and compilers are doing things out of spec.
The following is my understanding. (per JLS8, oracle javac 8)
In general we talk about generic class/interface declaration G<T extends B1>
; as an example
class Foo<T extends Number> { .. }
A generic declaration can be seen as a declaration of a set of concrete types; e.g. Foo
declares types Foo<Number>, Foo<Integer>, Foo<Float>, ...
no wildcard
A concrete type G<X>
(where X is a type) is well-formed iff X<:B1
, i.e. X
is a subtype of B1
.
Foo<Integer>
is well-formed because Integer<:Number
.
Foo<String>
is not well-formed; it doesn't exist in the type system.
This constraint is enforced rigorously, for example, this won't compile
<T> void m1(Foo<T> foo) // Error, it's not the case that T<:Number
? super
Given a type G<? super B2>
, we would expect that B2<:B1
. This is because we most often need to apply capture conversion on it, resulting in G<X> where B2<:X<:B1
, implying B2<:B1
. If B2<:B1
is false, we'll introduce contradiction in the type system, leading to bizzare behaviors.
In fact, Foo<? super String>
is rejected by javac, which is nice, because the type is apparently a programmer's error.
Interestingly, we cannot find this constraint in JLS; or at least, it is not clearly stated in JLS. And experiments show that javac
does not always enforce this constraint, for example
<T> Foo<? super T> m2() // compiles, even though T<:Number is false
<String>m2(); // compiles! returns Foo<? super String> !
It's unclear why they are allowed. I'm not aware of any problem this would cause in practice though.
? extends
Given G<? extends B2>
, capture conversion yields G<X> where X<:B1&B2
.
The question is when the intersection type B1&B2
is well-formed. The most liberal approach would be to allow any intersection; even if the intersection is empty, i.e. B1&B2
is equivalent to the null type, it won't cause problems in the type system.
But practically, we would want the compiler to reject things like Number&String
, introduced by Foo<? extends String>
, since in all likelihood it must be a programmer's error.
A more specific reason is that javac
needs to construct a "notional class" that is a subtype of B1&B2
so that javac can reason about what methods can be called on the type. For that purpose, Number&String
cannot be allowed, while Number&Integer
, Number&Object
, Number&Runnable
etc are allowed. This part is specified in JLS#4.9
String & Comparable<Double>
cannot be allowed, because the notional class would be implementing both Comparable<String>
and Comparable<Double>
, which is illegal in Java.
B1
and B2
can be in many forms, leading to more complicated cases. This is where the spec isn't very well thought out. For example, it's unclear, from the text of the spec, what if one of them is a type variable; the behavior of javac
does seem reasonable to us
<T extends Runnable> Foo<? extends T> m3() // error
<T extends Object > Foo<? extends T> m4() // error
<T extends Number > Foo<? extends T> m5() // ok
<T extends Integer > Foo<? extends T> m6() // ok
Another example, should Number & Callable<?>
be allowed? And if it is, what should be the notional class's super interfaces? Remember that Callable<?>
cannot be a super interface
class Bar extends Number implements Callable<?> // illegal
In an even more complicated case we have something like Foo<Number> & Foo<CAP#1>
where CAP#1
is a type variable introduced by capture conversion. The spec clearly forbids it, yet the use case indicates that it should be legitimate.
javac
handles these cases more liberally than JLS. See responses from Maurizio and Dan
? ? ?
So, what do we do about it as a programmer? - Follow your intuition and construct types that make sense to you. Most likely javac
would accept it. If not, it's probably a mistake on your part. In rare cases, the type makes sense, yet the spec/javac doesn't allow it; you are out of luck:) and you'll have to find workarounds.