0

I have rewritten this ArrayIndexComparator class based on this answer of Jon Skeet so that it can support generic types: Get the indices of an array after sorting?

public class ArrayIndexComparator<T extends Comparable<? super T>> implements Comparator<Integer> {
    private final T[] array;

    public ArrayIndexComparator(T[] array) {
        this.array = array;
    }

    public Integer[] createIndexArray() {
        Integer[] indexes = new Integer[array.length];
        for (int i = 0; i < array.length; i++) {
            indexes[i] = i; // Autoboxing
        }
        return indexes;
    }

    @Override
    public int compare(Integer index1, Integer index2) {
        // Autounbox from Integer to int to use as array indexes
        return array[index2].compareTo(array[index1]);
    }
}

And I can use this class as long as the T type or a supertype of T implements the Comparable interface (Comparable<? super T>).

If I write Comparable<? extends T> instead of Comparable<? super T>, the compiler gives me an error:

@Override
public int compare(Integer index1, Integer index2) {
    // Autounbox from Integer to int to use as array indexes
    return array[index2].compareTo(array[index1]); // ERROR -> compareTo (capture<? extends T>) in Comparable cannot be applied to (T)
}

What does this compareTo (capture<? extends T>) in Comparable cannot be applied to (T) error mean?

I can assume that it does not make sense to define a class AClass which implements Comparable<AChildClassOfAClass>, but yet it is possible:

class AClass implements Comparable<AChildClassOfAClass> {


    @Override
    public int compareTo(AChildClassOfAClass o) {
        return 0;
    }
}

class AChildClassOfAClass extends AClass {
}

In this strange scenario, I assume to be able to use ArrayIndexComparator if I have defined it as ArrayIndexComparator<T extends Comparable<? extends T>>, because in this case T is AClass which implements Comparable<AChildClassOfAClass> which turns out to be a subclass of AClass (therefore Comparable<? extends T>Comparable<AChildClassOfAClass extends AClass>).

Of course, it makes more sense to use Comparable<? super T>, but I guess I should be able to do it anyway if I want to.

Why doesn't Java allow me to do this?

Thank you very much for your attention.

EDIT: after reading answer to the question Difference between <? super T> and <? extends T> in Java, here is what I understood and I hope I understood it correctly:

array[index1] is of type T but compareTo would expect a type <? extends T>, i.e. an unknown specific type of T (or T itself, but the compiler can't assure that).

Therefore the compiler can't safely cast T to the specific unknown type, whereas it could do it if we use <? super T> because in this case compareTo would expect an unknown supertype of T as a parameter, and as we are passing T, the compiler can safely cast the type T to its supertype as a type T is also a supertype of T (like when we say that an Integer is also a Number, but a Number is not necessarily an Integer).

In this Comparable<? extends T> case we are trying to consume array[index1] by passing it to the compareTo method, but the compiler can't safely cast the type T to the specific type required by compareTo(<? extends T>), which is unknown.

That's why I get the error.

Are this thoughts right?

tonix
  • 5,862
  • 10
  • 61
  • 119
  • Possible duplicate of [Difference between super T> and extends T> in Java](https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java) – David Lavender Dec 06 '17 at 11:59
  • 1
    The only type-safe assumption the compiler can make is that an instance of T can be treated as an instance of a supertype of T (when passed to `compareTo()`). It can not, on the other hand, safely cast any possible instance of T to any possible subtype of T (which is what it would have to do with `Comparable extends T>` was used). `compareTo` is a consumer function here. Consumers super. – david a. Dec 06 '17 at 12:13

2 Answers2

1

This kind of error generally means you're trying to encode a constraint that doesn't make sense. In your case, we already know there's something that doesn't make sense:

in this case T is AClass which implements Comparable<AChildClassOfAClass>

This is the real issue. Your classes allow you to do base.compareTo(child), but not child.compareTo(base). You've tweaked the generic parameters to handle this, but you've merely pushed the problem elsewhere (and luckily the compiler is clever enough to spot it!).

What the error is actually saying

compareTo takes a ? extends T. i.e. some specific yet unknown type that extends T. The compiler is complaining because we're attempting to pass in a T - that won't be the same as (nor assignable to) the unknown type, in general. The only thing we can pass into such a method is null.

Further reading

Oliver Charlesworth
  • 252,669
  • 29
  • 530
  • 650
  • Thank you very much for your answer, I am sorry, but still I don't get it. What if still do not want to do `child.compareTo(base)`, I should be able to do it, shouldn't I? – tonix Dec 06 '17 at 11:53
  • @tonix - But your code is doing `a.compareTo(b)`, where all that's known is that `a` and `b` are both of type `T`. `T` could be either base or child in your scenario. – Oliver Charlesworth Dec 06 '17 at 11:53
  • Maybe I start to understand. In this line `return array[index2].compareTo(array[index1]);` both array[index2] and `array[index1]` are of type `T`. But my doubt now is, desn't `? extends T` comprehend `T + any subtype of T`? – tonix Dec 06 '17 at 12:09
  • 1
    @tonix - No, that would only be the case if the method took a `T`. But it takes `? extends T`, i.e. the compiler can't know what *specific* type is valid. So the only thing you can pass in here is `null`. – Oliver Charlesworth Dec 06 '17 at 12:12
  • I read the answer https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java and will read your further readings as soon as I have time. Please, could you tell me if what I wrote in my EDIT is correct? Thank you very much – tonix Dec 07 '17 at 14:35
  • 1
    @tonix - Yes, the stuff in your EDIT looks exactly correct to me :) – Oliver Charlesworth Dec 07 '17 at 16:32
-1

To manage a ? generic type argument, the compiler uses "captures"; in your case, the capture-of-? super T.

The generic term ? super T means "any superclass of T"; so note it can be Object; so you might have a Comparable<Object> when that method is called. However, this is not valid:

Comparable<AChildClassOfAClass> yourComparable == new YourClass();
yourComparable.compareTo(new Object());

which is what your code might get into with that definition.

daniu
  • 12,131
  • 3
  • 23
  • 46