Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Collections.reverseOrder() method in Java

Tags:

java

generics

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>.

like image 271
Edgar Rokjān Avatar asked Nov 27 '15 22:11

Edgar Rokjān


People also ask

What does reverseOrder () do in Java?

Java Collections reverseOrder() Method The reverseOrder() method of Java Collections class is used to get the comparator that imposes the reverse of the natural ordering on a collection of objects which implement the Comparable interface.

What is comparator reverseOrder?

The reverseOrder() method of Comparator Interface in Java returns a comparator that use to compare Comparable objects in reverse of natural order. The returned comparator by this method is serializable and throws NullPointerException when comparing null.

How do you reverse an array in a collection?

reverse() Method in Java with Examples. reverse() method of Collections class as the name itself suggests is used for reversing elements been there up in the object in which they are stored. It reverses the order of elements in a list passed as an argument. Parameter: Object of a class whose elements are to be reversed ...

How do you sort in descending order with Collections?

Approach: An ArrayList can be Sorted by using the sort() method of the Collections Class in Java. This sort() method takes the collection to be sorted and Collections. reverseOrder() as the parameter and returns a Collection sorted in the Descending Order.


2 Answers

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.

like image 161
Mike Nakis Avatar answered Oct 04 '22 18:10

Mike Nakis


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()
like image 33
Paul Boddington Avatar answered Oct 04 '22 18:10

Paul Boddington