3

In collections casting to Comparable interface is often used, eg. in PriorityQueue:

private static <T> void siftUpComparable(int k, T x, Object[] es) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    ...
        if (key.compareTo((T) e) >= 0)
            break;
    ...
}

Say, we create queue of integers and add something:

PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(1);

If I get the concept of wildcards right, the only effect of using <? super T> instead of <T> is that compiler extends the possible argument type of compareTo

public interface Comparable<T> {
    public int compareTo(T o);
}

to any superclass of Integer. But I'm wondering why and how it can be used here. Any example where

Comparable<T> key = (Comparable<T>) x;

is not enough? Or it's kind of guideline to use "super" wildcard with Comparable?

A.King
  • 172
  • 2
  • 11

3 Answers3

3

Relaxing the generic signature can raise the flexibility of code, especially when we’re talking about parameter types. The general guideline is the PECS rule, but for Comparable, practical examples of needing this flexibility are rare, as these imply having a base type defining a natural order among all of its subtypes.

E.g., if you have a method like

public static <T extends Comparable<T>> void sort(Collection<T> c) {

}

you can not do tho following

List<LocalDate> list = new ArrayList<>();
sort(list); // does not compile

The reason is that LocalDate implements ChronoLocalDate and you can compare all implementations of ChronoLocalDate with each other, in other words, they all implement Comparable<ChronoLocalDate>.

So, the method signature has to be

public static <T extends Comparable<? super T>> void sort(Collection<T> c) {

}

to allow actual types to implement Comparable parameterized with a super type, just like Collections.sort has been declared.


For the specific case of siftUpComparable, i.e. an internal Collection method which has no chance to determine the actual generic signature, it indeed doesn’t matter whether it uses Comparable<T> or Comparable<? super T>, as all it needs, is the ability to pass an instance of T to the compare method, whereas even providing the actual argument is done by another unchecked cast anyway.

In fact, since the type cast is unchecked, it could have been a cast to Comparable<Object> as well, which would eliminate the need to do the (T) cast at the compare invocation.

But obviously, the author did not intend to move too far away from what actual generic code would do and used the same pattern, even if it does not have an obvious benefit here.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • excellent answer! I saw this as a pattern and my previous job and never understood why, I kept saying to myself - I miss something... – Eugene Feb 26 '19 at 16:48
2

E might not actually expose a comparable for its own class. Imagine this case:

class Foo implements Comparable<Foo> { ... }

class Bar extends Foo { ... }

If you make a priority queue of PriorityQueue<Bar>, then you use the comparison method defined in Foo. ie, you have a Comparable<? super Bar> where the wildcard is realized as Foo

flakes
  • 21,558
  • 8
  • 41
  • 88
  • Tested, this works without wildcard as well. How can you actually not expose `Comparable` to subclass? I mean `bar1.compareTo(bar2)` will be legal anyway (even if `compareTo` gets argument of `Foo` type). – A.King Feb 13 '19 at 23:59
  • 1
    @A.King Casting does not work the same in generic type arguments as it does for the top level classes. For example, you can't directly cast a `List` to a `List` like you can an `ArrayList` to a `List`. This is why the `?` wildcard and `extend`/`super` syntax exist. – flakes Feb 14 '19 at 00:05
  • I seem to get how wildcard works in this example, but still, it's no different from option without the wildcard. If I explicitly cast to `Comparable` it compiles and works the same as with `Comparable`. – A.King Feb 14 '19 at 11:05
  • 1
    @A.King You performed an Unsafe-Cast and only works by chance due to a limitation of the generic class/method scope. You likely have a warning to tell you as much. The only reason that this works is due to type erasure at runtime; both `Comparable` and `Comparable` will be reduced to `Comparable` at runtime. In a non generic context, `Comparable b =(Comparable) new Bar();` will fail to compile with `error: incompatible types: Bar cannot be converted to Comparable`. Instead it must be defined as `Comparable super Bar> b = new Bar();` – flakes Feb 14 '19 at 14:28
1

The whole point of generics is being type safe. Since Java generics lose type information at runtime, we don't have a method of validating this cast. Bounded wildcards are a safe way of validating a type restriction at compile time, rather than making a hail Mary cast at runtime.

To understand the different semantics of super and extends in generics, consult this thread:
What is PECS (Producer Extends Consumer Super)?

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588