Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bounding generics with 'super' keyword

Why can I use super only with wildcards and not with type parameters?

For example, in the Collection interface, why is the toArray method not written like this

interface Collection<T>{     <S super T> S[] toArray(S[] a); } 
like image 800
mohsenof Avatar asked May 10 '10 04:05

mohsenof


People also ask

How are bounds used with generics?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class. These are known as bounded-types in generics in Java.

How do you declare a generic bounded type parameter in Java?

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound, which in this example is Number . Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

Can a parameterized type have several bounds?

A type parameter can have multiple bounds.

Can generics take multiple type parameters?

Multiple parametersYou can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.


2 Answers

super to bound a named type parameter (e.g. <S super T>) as opposed to a wildcard (e.g. <? super T>) is ILLEGAL simply because even if it's allowed, it wouldn't do what you'd hoped it would do, because since Object is the ultimate super of all reference types, and everything is an Object, in effect there is no bound.

In your specific example, since any array of reference type is an Object[] (by Java array covariance), it can therefore be used as an argument to <S super T> S[] toArray(S[] a) (if such bound is legal) at compile-time, and it wouldn't prevent ArrayStoreException at run-time.

What you're trying to propose is that given:

List<Integer> integerList; 

and given this hypothetical super bound on toArray:

<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java 

the compiler should only allow the following to compile:

integerList.toArray(new Integer[0]) // works fine! integerList.toArray(new Number[0])  // works fine! integerList.toArray(new Object[0])  // works fine! 

and no other array type arguments (since Integer only has those 3 types as super). That is, you're trying to prevent this from compiling:

integerList.toArray(new String[0])  // trying to prevent this from compiling 

because, by your argument, String is not a super of Integer. However, Object is a super of Integer, and a String[] is an Object[], so the compiler still would let the above compile, even if hypothetically you can do <S super T>!

So the following would still compile (just as the way they are right now), and ArrayStoreException at run-time could not be prevented by any compile-time checking using generic type bounds:

integerList.toArray(new String[0])  // compiles fine! // throws ArrayStoreException at run-time 

Generics and arrays don't mix, and this is one of the many places where it shows.


A non-array example

Again, let's say that you have this generic method declaration:

<T super Integer> void add(T number) // hypothetical! currently illegal in Java 

And you have these variable declarations:

Integer anInteger Number aNumber Object anObject String aString 

Your intention with <T super Integer> (if it's legal) is that it should allow add(anInteger), and add(aNumber), and of course add(anObject), but NOT add(aString). Well, String is an Object, so add(aString) would still compile anyway.


See also

  • Java Tutorials/Generics
    • Subtyping
    • More fun with wildcards

Related questions

On generics typing rules:

  • Any simple way to explain why I cannot do List<Animal> animals = new ArrayList<Dog>()?
  • java generics (not) covariance
  • What is a raw type and why shouldn’t we use it?
    • Explains how raw type List is different from List<Object> which is different from a List<?>

On using super and extends:

  • Java Generics: What is PECS?
    • From Effective Java 2nd Edition: "producer extends consumer super"
  • What is the difference between super and extends in Java Generics
  • What is the difference between <E extends Number> and <Number>?
  • How can I add to List<? extends Number> data structures? (YOU CAN'T!)
like image 62
polygenelubricants Avatar answered Oct 03 '22 02:10

polygenelubricants


As no one has provided a satisfactory answer, the correct answer seems to be "for no good reason".

polygenelubricants provided a good overview of bad things happening with the java array covariance, which is a terrible feature by itself. Consider the following code fragment:

String[] strings = new String[1]; Object[] objects = strings; objects[0] = 0; 

This obviously wrong code compiles without resorting to any "super" construct, so array covariance should not be used as an argument.

Now, here I have a perfectly valid example of code requiring super in the named type parameter:

class Nullable<A> {     private A value;     // Does not compile!!     public <B super A> B withDefault(B defaultValue) {         return value == null ? defaultValue : value;     } } 

Potentially supporting some nice usage:

Nullable<Integer> intOrNull = ...; Integer i = intOrNull.withDefault(8); Number n = intOrNull.withDefault(3.5); Object o = intOrNull.withDefault("What's so bad about a String here?"); 

The latter code fragment does not compile if I remove the B altogether, so B is indeed needed.

Note that the feature I'm trying to implement is easily obtained if I invert the order of type parameter declarations, thus changing the super constraint to extends. However, this is only possible if I rewrite the method as a static one:

// This one actually works and I use it. public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... } 

The point is that this Java language restriction is indeed restricting some otherwise possible useful features and may require ugly workarounds. I wonder what would happen if we needed withDefault to be virtual.

Now, to correlate with what polygenelubricants said, we use B here not to restrict the type of object passed as defaultValue (see the String used in the example), but rather to restrict the caller expectations about the object we return. As a simple rule, you use extends with the types you demand and super with the types you provide.

like image 23
Rotsor Avatar answered Oct 03 '22 00:10

Rotsor