Difference Between ≪? Super T≫ and ≪? Extends T≫ in Java

Difference between ? super T and ? extends T in Java

extends

The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>(); // Double extends Number
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to read from List foo3:

    • You can read a Number because any of the lists that could be assigned to foo3 contain a Number or a subclass of Number.
    • You can't read an Integer because foo3 could be pointing at a List<Double>.
    • You can't read a Double because foo3 could be pointing at a List<Integer>.
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can't add an Integer because foo3 could be pointing at a List<Double>.
    • You can't add a Double because foo3 could be pointing at a List<Integer>.
    • You can't add a Number because foo3 could be pointing at a List<Integer>.

You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

super

Now consider List <? super T>.

The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>(); // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>(); // Object is a superclass of Integer
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to receive when you read from List foo3:

    • You aren't guaranteed an Integer because foo3 could be pointing at a List<Number> or List<Object>.
    • You aren't guaranteed a Number because foo3 could be pointing at a List<Object>.
    • The only guarantee is that you will get an instance of an Object or subclass of Object (but you don't know what subclass).
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can add an Integer because an Integer is allowed in any of above lists.
    • You can add an instance of a subclass of Integer because an instance of a subclass of Integer is allowed in any of the above lists.
    • You can't add a Double because foo3 could be pointing at an ArrayList<Integer>.
    • You can't add a Number because foo3 could be pointing at an ArrayList<Integer>.
    • You can't add an Object because foo3 could be pointing at an ArrayList<Integer>.

PECS

Remember PECS: "Producer Extends, Consumer Super".

  • "Producer Extends" - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list.

  • "Consumer Super" - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list.

  • If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List<Integer>.

Example

Note this example from the Java Generics FAQ. Note how the source list src (the producing list) uses extends, and the destination list dest (the consuming list) uses super:

public class Collections { 
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}

Also see
How can I add to List<? extends Number> data structures?

What is the difference between 'super' and 'extends' in Java Generics

See Effective Java 2nd Edition, Item 28:

PECS

Producer extends, Consumer super

If your parameter is a producer, it should be <? extends T>, if it's a consumer it has to be <? super T>.

Take a look at the Google Collections, they know how to use it, because they got Bloch ;)

What is ? super T syntax?

super in Generics is the opposite of extends. Instead of saying the comparable's generic type has to be a subclass of T, it is saying it has to be a superclass of T. The distinction is important because extends tells you what you can get out of a class (you get at least this, perhaps a subclass). super tells you what you can put into the class (at most this, perhaps a superclass).

In this specific case, what it is saying is that the type has to implement comparable of itself or its superclass. So consider java.util.Date. It implements Comparable<Date>. But what about java.sql.Date? It implements Comparable<java.util.Date> as well.

Without the super signature, SortedList would not be able accept the type of java.sql.Date, because it doesn't implement a Comparable of itself, but rather of a super class of itself.

What is the difference between ? extends Base and T extends Base?

By defining the method with the following signature:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

and invoking it like:

compiles(new HashMap<Integer, List<Integer>>());

you're matching T against the type you're providing.

In the jls §8.1.2 we find, that (interesting part bolded by me):

A generic class declaration defines a set of parameterized types (§4.5), one for each possible invocation of the type parameter section by type arguments. All of these parameterized types share the same class at run time.

In other words, the type T is matched against the input type and assigned Integer. The signature will effectively become static void compiles(Map<Integer, List<Integer>> map).

When it comes to doesntCompile method, jls defines rules of subtyping (§4.5.1, bolded by me):

A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):

  • ? extends T <= ? extends S if T <: S

  • ? extends T <= ?

  • ? super T <= ? super S if S <: T

  • ? super T <= ?

  • ? super T <= ? extends Object

  • T <= T

  • T <= ? extends T

  • T <= ? super T

This means, that ? extends Number indeed contains Integer or even List<? extends Number> contains List<Integer>, but it's not the case for Map<Integer, List<? extends Number>> and Map<Integer, List<Integer>>. More on that topic can be found in this SO thread. You can still make the version with ? wildcard work by declaring, that you expect a subtype of List<? extends Number>:

public class Example {
// now it compiles
static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

public static void main(String[] args) {
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}

How do ListT and List? extends T differ in these examples?

In your example, this does not much make much difference:

  • List<? extends T>
  • List<T>

because you're calling a generic method itself, which on each call can adjust T to match exactly your input. So no matter if you're calling

final List<Vehicle> vehicles = new ArrayList<>();
System.out.println(findSmallestIndex(vehicles));

or

final List<Car> cars = new ArrayList<>();
System.out.println(findSmallestIndex(cars));

the type T

  • in the first call will be Vehicle (so the parameter itemList is List<Vehicle>),
  • and in the second call T will be Car (so the parameter itemList is List<Car>).

So why does Java have the 'extends'/'super' magic?

The short answer is: manual control of covariance and contravariance (because pure Java Generics are invariant)

And for the long answer, check the following code:

We have our own List:

static class MyList<T> {
public boolean containsAll(final List<T> pItem) {
return false;
}
public boolean containsAll_extends(final List<? extends T> pItem) {
return false;
}
public boolean containsAll_super(final List<? super T> pItem) {
return false;
}
}

And we use it in this way:

public static void main(final String[] args) {
final MyList<Vehicle> vehicles = new MyList<>();
final MyList<Car> cars = new MyList<>();

final ArrayList<Vehicle> otherVehicles = new ArrayList<>();
final ArrayList<Car> otherCars = new ArrayList<>();

{ // Try #1
vehicles.containsAll(otherVehicles);
vehicles.containsAll(otherCars); // will NOT compile!

cars.containsAll(otherVehicles); // will NOT compile!
cars.containsAll(otherCars);
}


{// Try #2
vehicles.containsAll_extends(otherVehicles);
vehicles.containsAll_extends(otherCars);

cars.containsAll_extends(otherVehicles); // will NOT compile!
cars.containsAll_extends(otherCars);
}

{// Try #3
vehicles.containsAll_super(otherVehicles);
vehicles.containsAll_super(otherCars); // WILL NOT compile!

cars.containsAll_super(otherVehicles);
cars.containsAll_super(otherCars);
}
}

Observations:

  • This time MyList<Vehicle> vehicles will be fixed to the type MyList<Vehicle>
  • and thus the inner T of vehicles will always be Vehicle
  • this usage in a generic Class/Object is opposed to your use of a generic method.

    • Remember: each time you called the generic method, the type T could be freely adjusted to the input.

    • Here in my example, the type T (of vehicles) is locked to type Vehicle (and the type T of cars is locked to type Car)

Try #1:

  • thus the method vehicles.containsAll() will accept List<T> => List<Vehicle>
  • it would also accept ArrayList<T> => ArrayList<Vehicle>, because we can cast ArrayList<Vehicle> into List<Vehicle>
  • it would NOT accept List<Car> or ArrayList<Car>, because we CANNOT cast List<Car> into List<Vehicle> because the generic parameter is invariant

    • you can still add Car (or any other subclass of Vehicle) to List<Vehicle>, but you can not cast List<Car> into List<Vehicle>

    • I added an example of casting lists at the bottom of this answer

So, we give it some variance, introducing:

  • <? super T> for contravariance
  • <? extends T> for covariance

So now we can do: check Try #2:

  • vehicles.containsAll_extends() accepts List<? extends T>, so any List where ? is the subclass (or class itself) of T
  • T is still bound as Vehicle, so it will accept any List of a subclass of Vehicle
  • otherCars is of type ArrayList<Car>, which fits the description, because

    • ArrayList is a subclass of List

    • and Car is a subclass of Vehicle
  • so vehicles.containsAll_extends(otherCars); is valid and will be accepted by the compiler
  • because both relations (ArrayList -> List) and (Car -> Vehicle) are "is subclass of", this is called "co-variant", so varying in(to) the same direction

And what can we learn from Try #3?

  • This demonstrates contra-variance

    • ArrayList is a subclass of List

    • and Vehicle is a superclass of Car
  • thus this is called contra-variance

Casting Lists:

This example shows how we can use variance with Generics so we can allow more flexibility:

    final ArrayList<Vehicle> otherVehicles = new ArrayList<>();
final ArrayList<Car> otherCars = new ArrayList<>();

final ArrayList<? extends Vehicle> otherVehicles2 = new ArrayList<>();

otherVehicles = otherVehicles2; // will NOT compile!
otherVehicles = otherCars; // will NOT compile!
otherVehicles2 = otherVehicles;
otherVehicles2 = otherCars;

Best Practices:

  1. You want to keep your output as specific as possible:
  • use the most specific types:

    • do NOT write List<Vehicle> list = new ArrayList<Vehicle>();, because the information that it's an ARRAYList will get lost (to the coder; at runtime, Java will still know it's an ArrayList, but you and fellow programmers might get some ugly time checking it with instanceof if you abandon the explicit type in the code)

    • instead, write ArrayList<Vehicle> list = new ArrayList<Vehicle>(); or ArrayList<Vehicle> list = new ArrayList<>(); for Java 1.7+

    • preserving that information often makes a difference later, for example when using index-based sorting, or relying on a Map being sorted (SortedMap or TreeMap as opposed to the 'chaotic sorting' of HashMap)

  1. You want to keep your input as generic as possible (as long as the code stays clean)
  • so for most testing/adding methods, allow all classes you can work on; for generic types that is usually subclasses, for the main type (ArrayList) it's very often superclasses (so usually a contravariant approach)

    • instead of writing boolean containsItems(ArrayList<Vehicle> items) {...}

    • you want to write boolean containsItems(List<? extends Vehicle> items) {...} (contravariant in main type: ArrayList->List, covariant on the generics: -> <? extends Vehicles>)

    • or even write boolean containsItems(Collection<? extends Vehicle> items) {...} (more contravariance on main type: List->Collection)

    • or even better: boolean containsItems(Iterable<? extends Vehicle> items) {...} if you only want to iterate over the items (even more contravariance on main type: Collection->Iterable)

    • note: ArrayList extends List implements Collection implements Iterable, so you go up that inheritance tree as far as it's acceptable for your code
  • in contrast boolean containsItems(Object items) {...}

    • will do you no good, because then you'd have to manually check for all possible types

    • and use a lot of Reflection to ensure type safety of generic parameters, which results in a ton of code

    • in Java, this all is meant to be handled by the compiler, to get compile time errors (red underlines in your code). This is why Java uses strong/strict types in the first place, to save us from RuntimeExceptions (ClassCastException) and to make the code safe to run (no accidental writing/reading into unrelated RAM)

What is a difference between ? super E and ? extends E?

The first (<? super E>) says that it's "some type which is an ancestor (superclass) of E"; the second (<? extends E>) says that it's "some type which is a subclass of E". (In both cases E itself is okay.)

So the constructor uses the ? extends E form so it guarantees that when it fetches values from the collection, they will all be E or some subclass (i.e. it's compatible). The drainTo method is trying to put values into the collection, so the collection has to have an element type of E or a superclass.

As an example, suppose you have a class hierarchy like this:

Parent extends Object
Child extends Parent

and a LinkedBlockingQueue<Parent>. You can construct this passing in a List<Child> which will copy all the elements safely, because every Child is a parent. You couldn't pass in a List<Object> because some elements might not be compatible with Parent.

Likewise you can drain that queue into a List<Object> because every Parent is an Object... but you couldn't drain it into a List<Child> because the List<Child> expects all its elements to be compatible with Child.

Java wildcards and generics ? super T and ? extends T

you should have a look at the explanation of PECS principle

What is PECS (Producer Extends Consumer Super)?

In short, when you want to get information from an object, make sure to use extends with the wild card.

And when you want to put information into an object, make sure to use super along with wild card



Related Topics



Leave a reply



Submit