Issue with Comparator.comparing
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Thu Dec 13 12:29:59 UTC 2018
On 12/12/2018 23:44, B. Blaser wrote:
> It's interesting to see that your example fails when invoking
> 'Integer::compareTo' but not when invoking 'Comparator::compare':
Actually, the failure happens in this line:
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
Where, c is a Comparator<? super T>, and the array to be sorted is a T[].
The comparator generated by Comparator.comparing is then failing
because, if you look at how the method is implemented:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) ->
keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
It is clear that the method implementation relies on the assumption that
keyExtractor returns homogeneous things.
But note that, even if the signature was made stricter, as in:
public static <T, U extends Comparable<U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor) { ... }
The example I posted in my previous email would still compile - because
U is inferred as a capture variable generated during method reference
typing. In other words, the compiler *thinks* that the method reference
Foo::t is really returning things that can be compareTo with each other,
which is a flawed assumption!
Note that, what you say:
> Semantically, 'List<Foo<?>>' means potentially heterogeneous, not
definitely.
Is already an hint that something is going wrong - generic types are
universally qualified things - whatever we prove for them must hold FOR
ALL possible instantiation. Which seems clearly *not* to be the case
here, as it's easy to think of examples where allowing the above list to
be treated as an homogeneous comparable collection would lead to issues.
Concluding, I really believe that the capture conversion introduced by
JDK-8029307, which was kind of a forced move (to type check a method
reference you have to reason about membership, which implies capture of
the receiver), is messing things up here, because, from a type system
perspective, it makes it look as if all possible calls to the functional
interface derived from that method reference is _always_ going to yield
the _same_ captured type, where in reality different captures will be
emitted. This is what made me think that this is indeed a type system
issue, very close (if not the same) to the one described in JDK-8170887.
Maurizio
More information about the compiler-dev
mailing list