Garret Wilson
Garret Wilson

Reputation: 21436

Java comparator for sorting double-nested generics array

With Java 17 I want to compare map entries based upon some criteria, where the map entries are Java classes associated with some value, like this:

Comparator<Map.Entry<Class<?>, Integer>> MY_COMPARATOR =
    new Comparator<Map.Entry<Class<?>, Integer>>() { … }

Later I have a list of a list of classes and their associated values (whether it's counts, memory size, or whatever). The twist is that my code is so refined (tongue in cheek) that I happen to know that all these classes extend from FooBar:

List<Map.Entry<Class<? extends FooBar>, Integer>> foobarValues = new ArrayList<>();

Now I just want to sort those map entries using MY_COMPARATOR (to find the smallest/largest classes, the classes most encountered, or whatever MY_COMPARATOR uses as its criteria). But this doesn't work:

java.util.Collections.sort(foobarValues, MY_COMPARATOR);

Java (actually Eclipse 2022-09) tells me:

The method sort(List<T>, Comparator<? super T>) in the type Collections is not applicable for the arguments (List<Map.Entry<Class<? extends FooBar>,Integer>>, Comparator<Map.Entry<Class<?>,Integer>>)

I've been working with Java generics for a long time, and my best simplified explanation of what's happening is that the ? super T in Collections.sort() "hides" the lower layer of generics, and that ? super Map.Entry<Class<?>> has no relation to ? super Map.Entry<Class<? extends FooBar>>. (But still, really?.) Actually my mind can't quite grasp the full intricacies of the problem.

But let's say I don't care so much about the problem; I just want the code to work with no warnings. You and I know that the comparator can work with any class that is possible to exist, so it will never cause a ClassCastException and it will never cause heap pollution, and it will never even sneak into your house at night and kill your bamboo plant.

How can I tell Java that it's OK for me to use this comparator? This does not work:

Collections.sort(foo, (Comparator<Map.Entry<Class<? extends FooBar>, Integer>>)MY_COMPARATOR);

I would be happy casting the list being sorted, but this doesn't work either:

Collections.sort((List<Map.Entry<Class<?>, Integer>>)foo, MY_COMPARATOR);

This does work!

Collections.sort(foo, (Comparator<Map.Entry<Class<? extends FooBar>, Integer>>)(Object)comparator);

But to use it I have to add @SuppressWarnings("unchecked"), and Java 17 javac with -Xlint:all still complains, and besides I feel dirty using it.

I would be content with some workaround that would allow me to suppress the warning with @SuppressWarnings("unchecked") as long as javac lint still didn't complain. But the intermediate cast to Object is too much of a kludge for me to accept.

What should I be doing?

Upvotes: 1

Views: 81

Answers (1)

Sweeper
Sweeper

Reputation: 272750

I would suggest that you declare the comparator by applying the PECS principle. It should have the type:

Comparator<Map.Entry<? extends Class<?>, Integer>>

Both type parameters for Map.Entry should have ? extends (though just the first one is enough to fix the error) because a map entry is a producer of its keys and value types.

You could also add ? super to the type parameter of Comparator, since the comparator is a consumer of map entries, but that is not needed here because it is actually already written in the method signature of sort.

void sort(Comparator<? super E> c)

After that, you can write:

foobarValues.sort(MY_COMPARATOR);

If you cannot change the type of MY_COMPARATOR, one way to get the compiler to "just shut up" is to cast to Object first:

(Comparator<Map.Entry<Class<? extends FooBar>, Integer>>) (Object) MY_COMPARATOR

Casting from Object to a parameterised type is an unchecked cast, which is presumably what you are looking for.

This unchecked cast would be safe as far as I know, since a map entry cannot be used as a consumer of its key type. You can only get, but not set, its key.

Upvotes: 1

Related Questions