One of the things people seem to find confusing is the distinction in wildcard or captured generics between extends
and super
.
Wild Card extends
One issue with Java generics is that even though String
extends CharSequence
, List<String>
does not extend List<CharSequence>
.
The most specific variable to which we can assign either list type is List<? extends CharSequence>
, which is extended by both List<String>
and List<CharSequence>
. We can assign an object of type List<E>
to a variable with static type List<? extends CharSequence>
as long as E
extends CharSequence
.
Wild Cards super
The super
keyword moves in the inheritance hierarchy in the opposite direction from extends
. A variable of type List<? super String>
may be assgined an object List<E>
if E is a supertype of String
(equivalent, String
extends E). Thus a variable of type List<? super String>
may be assigned an object of type List<String>
or of type List<CharSequence>
.
What about List<StringBuilder>
(hint: StringBuilder
implements CharSequence
)? A variable of type List<StringBuilder>
may be assigned to List<? extends CharSequence>
, but not List<? super String>
, because StringBuilder
extends CharSequence
(by implementing it), but StringBuilder
is not a supertype of String
.
Function Inputs and Outputs
Suppose we have a simple interface, Foo<A,B>
, with two generic arguments, A
for input, and B
for output:
interface Foo<A,B> { B apply(A x); }
Functions present a further problem in that their inputs and outputs generalize in different directions. In our example Foo<A,B>
, A
is a function input. A more general function takes a more general input than A. That means a more general type is Foo<? super A,B>
. It can accept any input to apply()
that Foo<A,B>
could and more.
But what about outputs? In order for an output type to be used wherever a B
could be used, it must extend B
so that it implements everything required for a B
. Therefore, a more general result type is Foo<A,? extends B>
. An object assigned to this type is known to provide outputs that at least implement everything a B
would.
Function Argument Polarity
This is related to the polarity of a type variable in a functional type. Basic types are positive, and arguments to functions reverse polarity. For example, if A and B are primitive types, for a function from A to B (of type A->B), A is negative polarity and B positive polarity.
Multiple arguments are all negative, so A->(B->C) has C as positive and A and B as negative.
Compound arguments further reverse polarity, so in (A->B)->C, C is positive, and (A->B) is negative, so B is negative, and A is positive again.
In general, you want to extend positive polarity arguments and super negative polarity arguments in order to be able to use the compound object wherever the original object could be used.
What if an Generic Parameter is Input and Output?
That’s actually the case with List<E>
, because the get()
method returns an E
result, whereas the add()
method consumes an E
argument. Thus there’s really no way to generalize the type of a collection. If you generalize List<E>
by subclassing E
, you restrict the set of inputs; if you generalize it by superclassing E
, you wind up with more general outputs that aren’t guaranteed to implement everything E
does.
Leave a Reply