10

Consider one of the overloaded definitions of sort method from Array class:

public static <T> void sort(T[] a, Comparator<? super T> c)

A common way to sort array in reverse order is to pass Comparator returned by Collections.reverseOrder() as a second argument to this method.

Let's look at the implementation of Collections.reverseOrder() method from openjdk 7:

public static <T> Comparator<T> reverseOrder() {
    return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}

ReverseComparator class:

private static class ReverseComparator
    implements Comparator<Comparable<Object>>, Serializable {

    private static final long serialVersionUID = 7207038068494060240L;

    static final ReverseComparator REVERSE_ORDER = new ReverseComparator();

    public int compare(Comparable<Object> c1, Comparable<Object> c2) {
        return c2.compareTo(c1);
    }

    private Object readResolve() { return reverseOrder(); }
}

My question is: why Collections.reverseOrder() is made to be generic? And why simply ReverseComparator.REVERSE_ORDER can't be returned?

Of course, we can specify type explicitly calling Collections.<T>reverseOrder(). But what's the benefit against simple Collections.reverseOrder() in this case?

I found a rather useful discussion there:

How does Collections.reverseOrder() know what type parameter to use?

But it doesn't answer to my question.

Also It's interesting for me how sort method uses compare method from ReverseComparator class. As we can see compare takes arguments of Comparable<Object> type. And what if we sort array of objects implementing Comparable<T>, where T is for example Integer? We can't invoke compare with Comparable<Integer> cause Comparable<Integer> isn't casted to Comparable<Object>.

Community
  • 1
  • 1
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67

2 Answers2

8

why Collections.reverseOrder() is made to be generic?

This function is generic so as to spare you from having to cast the result into your specific Comparable<T> type. (You might say that you don't care because you don't cast it anyway, in which case what this tells us is that you do not have enough warnings enabled.)

why we can't simply return ReverseComparator.REVERSE_ORDER?

One reason is because ReverseComparator.REVERSE_ORDER is package-private, so you cannot access it from outside that package. Which in turn begs the question "why is it package-private?" Well, mainly because this satisfies the purists who cringe when they see member variables being directly accessed even if they are final, but actually I would not blame them in this case, because accessors offer forward compatibility at the binary level, which might be completely unnecessary in application code, but it kind of becomes a necessity in a language runtime. And ReverseComparator is part of the java runtime.

But a more important reason is because Collections.reverseOrder() does the cast to (Comparator<T>) for you, so that you don't have to do it yourself. (Again, if you don't see a problem with this, that's because you do not have enough warnings enabled, which means you need to reconsider your practices.)

In short, if you tried to do the following:

Comparator<MyObject> myComparator = ReverseComparator.REVERSE_ORDER;

you would get an error, because this is an invalid assignment. So, you would have to change it to this:

Comparator<MyObject> myComparator = 
    (Comparator<MyObject>)ReverseComparator.REVERSE_ORDER;

but then you would get a warning, because this is an unchecked cast. So you would end up having to do this:

@SuppressWarnings( "unchecked" )
Comparator<MyObject> myComparator = 
    (Comparator<MyObject>)ReverseComparator.REVERSE_ORDER;

which is ugly. So, Collections.reverseOrder() saves you from that, allowing you to do this:

Comparator<MyObject> myComparator = Collections.reverseOrder();

As we can see compare takes arguments of Comparable type. And what if we sort array of objects implementing Comparable, where T is for example Integer? We can't invoke compare with Comparable cause Comparable isn't casted to Comparable.

Okay, I see what your real question is. Welcome to the wonderful world of java generics and type erasure. I will try to explain, but be sure to also look up the term "type erasure" in order to fully understand the concept.

In java, generics were introduced to the language as an afterthought. For this reason, they had to be implemented in such a way that generics-aware code would be backwards compatible with old code which was not generics-aware. The solution was a trick called type erasure, which basically means that generic information is completely stripped after compilation. This means that at the bytecode level, Comparator<String> and Comparator<Integer> and Comparator are one and the same thing. No difference whatsoever. This is what enables the java runtime to implement a single class which acts as a reverse comparator of any object. It is not really a Comparator<Object>, it is a Comparator<ANYTHING>, because all it does is reverse the direction of the comparison, so it does not really care about the nature of the objects that are being compared.

So, in java, if you really know what you are doing, you are free to cast an instance of a generic class to an instance of the same class, but with a different generic parameter. In this case, the creators of the java runtime are casting Comparator<Object> to Comparator<T>, which may in fact be later assigned to Comparator<Integer>, and that's fine.

This cast is tricky though, because the compiler has to trust that you really know what you are doing, so by default, the compiler issues an "unchecked assignment" warning on such casts, and then in turn we indicate that we swear we know what we are doing with a @SuppressWarnings( "unchecked" ) annotation.

Collections.reverseOrder() spares you from having to be concerned with all that.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • It *does* return `ReverseComparator.REVERSE_ORDER`, so the package-private issue is irrelevant. It just returns it **cast to `Comparator`**. – RealSkeptic Nov 27 '15 at 22:45
  • @RealSkeptic hmm, I see your point. I got confused as to what the OP meant by "return". I thought he was asking why don't we directly access `ReverseComparator.REVERSE_ORDER` instead of having `Collections.reverseOrder()` return it for us. That's because he wrote "we" instead if "it". – Mike Nakis Nov 27 '15 at 22:47
  • @MikeNakis I edited my post to explain last problem more deeply. Please, look at this edit. I didn't mention primitive types, I said about reference types differs from `Object'. – Edgar Rokjān Nov 27 '15 at 22:51
  • I see, I will amend my answer. – Mike Nakis Nov 27 '15 at 22:54
  • @MikeNakis I suppose that `Arrays.sort` method invokes `compare` method from `ReverseComparator` class to compare sorted objects. So, if we try to sort array of smth like `class Foo implemets Comparable` `compare` will be invoked with `Comparable` arguments instead of `Comparable` and it's forbidden. What's wrong with my logic? – Edgar Rokjān Nov 27 '15 at 23:33
  • @EdgarRokyan What's forbidden? – Paul Boddington Nov 27 '15 at 23:35
  • Well, what I have been trying to explain is that it is *not strictly forbidden*, because the bytecode for `Comparable` is exactly the same as the bytecode for `Comparable`. So, `Collections.reverseOrder()` makes use of this knowledge to cast one to the other, and you are fine. Try it. – Mike Nakis Nov 27 '15 at 23:35
  • @PaulBoddington to call `someFunc(Comparable arg)` with `Comaprable argument like `someFunc(x)`, where `x` is `Comparable` – Edgar Rokjān Nov 27 '15 at 23:51
  • 1
    @EdgarRokyan Yes it's forbidden, but only because the compiler forbids it. The compiler doesn't know it's safe. Usually it wouldn't be safe. For example, passing a `List` to a method expecting a `List` would be a bad idea because the method could add an `Integer` and then you'd have a `List` with an `Integer` in it. In the case of the comparator, it is safe because the reverse order comparator has no fields that depend on the type `T`, so it doesn't matter whether you think of it as being a `Comparator` or a `Comparator`. – Paul Boddington Nov 27 '15 at 23:59
  • 1
    @MikeNakis thanks for the detailed explanations, It helped me a lot. – Edgar Rokjān Nov 28 '15 at 20:31
2

This is all about type erasure. Remember, at runtime there is no such thing as a Comparable<Object>, there is only such a thing as a Comparable. Therefore the compare method of REVERSE_COMPARATOR works on two String instances, for example. It doesn't cause a ClassCastException at runtime because String implements Comparable<String>, and at runtime that's just Comparable.

However, the method reverseComparator has to be generic, because otherwise the user would have to cast the returned object to the appropriate type before it could be used. For example, consider this code where the comparator has the same type as the declared type of REVERSE_COMPARATOR.

Comparator<Comparable<Object>> comparator = Collections.reverseOrder();
String[] arr = {"A", "B", "C"};
Arrays.sort(arr, comparator);       // Doesn't compile.

The reason this doesn't compile is because arr is a String array, and so Arrays.sort expects a Comparator<? super String>, but Comparable<Object> is not a supertype of Comparable<String> (Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?).

You can make it compile by using casts:

Comparator<Comparable<Object>> comparator = Collections.reverseOrder();
String[] arr = {"A", "B", "C"};
Arrays.sort(arr, (Comparator<String>) (Object) comparator);
System.out.println(Arrays.toString(arr));   // prints [C, B, A]

This generates a warning, but as you will see it you try it, it works. By using a generic method, the ugliness of the cast and the warning is kept out of the code using the method.

The fact that the same object (REVERSE_COMPARATOR) can be treated as being a Comparator<String> or a Comparator<Integer>, or a Comparator<X> where X is any type implementing Comparable is one of the many benefits of type erasure. This reuse of objects is not possible in C# because in C# instances of a generic class know the type.

There are many examples of this kind of reuse. For example, all of these generic methods always return the same instance, no matter what generic type you supply.

Collections.emptySet()
Optional.empty()
Comparator.naturalOrder()
Collections.emptyListIterator()
Community
  • 1
  • 1
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • "but `Comparable` is not a supertype of `Comparable`" - Did you mean that `Comparable` isn't a super type of **String**? – Edgar Rokjān Nov 28 '15 at 16:05
  • Yes that is what I meant. I guess the point I was trying to make is that just because `Comparable` is a super type of `String` it doesn't mean that `Comparable` is. – Paul Boddington Nov 28 '15 at 16:33
  • @No problem. I'm glad it helped. Java generics are very difficult to explain. – Paul Boddington Nov 28 '15 at 20:35
  • For the most part, I'm a C++ developer. I met generics a couple of weeks ago and sometimes the drive me crazy. Looks like a great crunch in Java language. – Edgar Rokjān Nov 28 '15 at 20:39
  • @EdgarRokyan They're just far, far too complicated. I've earned more points on this site answering questions on generics than any other single topic, but it would take a lifetime to understand the interplay between wild cards, recursive bounds, type inference using `<>`, raw types etc etc. – Paul Boddington Nov 28 '15 at 20:43