Why is Comparator<T>
not Comparator<in T>
?
Good question!
I don’t have an answer, but my guess would be some issue with Java compatibility.
As you may know, in Java, you can only specify variance where a type is used, not where it’s declared. If you look at java.lang.Comparator
, you’ll see that it’s defined as interface Comparator<T>
— as there’s no other way to do it. Instead, a few of its methods have wildcards, (e.g. thenComparing(Comparator<? super T> other)
).
Kotlin, on the other hand, allows declaration-site variance too, in the way that you suggest. That builds knowledge about the variance into the class declaration, and avoids a lot of extra typing (and thinking) wherever it’s used.
Kotlin/JVM makes very heavy use of the existing Java standard library. In a few cases, it uses its own wrappers with tweaks to make things easier to use in Kotlin — for example, adding String.indices()
so you can loop over the characters in a String. That even extends to adding variance where appropriate; for example kotlin.collections.List
is defined as interface List<out E> : Collection<E>
(unlike the sub-interface MutableList
, which is invariant.)
But, as you point out, they didn’t do so for kotlin.Comparator
.
The direct reason is that that’s not a wrapper class, but a simple typealias for java.util.Comparator
. So it doesn’t have the option of changing variance, or anything else.
However, I don’t know why they didn’t make it a wrapper class. Using variance in the way you suggest is an obvious improvement — in fact, section 9.3.4 of the book Kotlin In Action (which was written by two members of the Kotlin development team, and is excellent!) gives interface Comparator<in T>
as an example when explaining contravariance!
Maybe they felt that ultimately a wrapper wasn’t justified; or maybe it revealed some variance problem in the Java class that couldn’t be fixed that way; or maybe it would cause some other problem interoperating with Java code that defines or uses comparators.
Note that you can still specify the variance when using a Comparator, e.g.:
val c: Comparator<in String> = //…
Thank you for your long reply.
The java.util.Comparator
interface has wildcards, but all wildcards are ? super T
which perfectly fits contravariance. Comparator
is one of the most prominent interfaces, so if it can’t be solved with a clean typealias I would have expected some intrinsic solution of which you have named a few.
Maybe it’s worth more investigation and a youtrack issue?
Testing around a little, the problem is actually the thenComparing
methods. The fact that T
is used as a parameter is breaking the contravariance. Kotlin has extension methods for this (called then
), so they are not actually needed and are just breaking things.
I guess it is too complicated to hide them in Kotlin and cover all the corner cases, like a comparator implementation that choses to override the default thenComparing
method (unlikely but possible).
Java 8 introduced these methods, so I wouldn’t be suprised if Kotlin originally provided contravariance on Comparator
and had to give it up for Java 8 interoperability.