Function Input/Output Polarity and Java Wildcard Generics: extends vs. super


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

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: