Reputation: 21436
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
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