Issue with Comparator.comparing
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Dec 17 16:59:11 UTC 2018
Since we're discussing this, I believe the closest you can get w/o
method reference is this:
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class Foo3<T extends Comparable<T>> {
static Foo3<String> S = new Foo3<>("");
static Foo3<Integer> I = new Foo3<>(42);
private T t;
Foo3(T t) {
this.t = t;
}
T t() {
return t;
}
public static void main(String[] args) {
List<Foo3<?>> l = new ArrayList<>();
l.add(S);
l.add(I);
Comparator<? super Foo3<?>> comp = makeComp();
Collections.sort(l, comp);
}
static <Z extends Foo3<?>> Comparator<Z> makeComp() {
return new Comparator<Z>() {
public int compare(Z f1, Z f2) {
return f1.t().compareTo(f2.t());
}
};
}
}
Note how this example doesn't use any raw types, and relies on inference
instead.
But, when this is compiled, you get an error:
error: incompatible types: Comparable<CAP#1> cannot be converted to CAP#2
return f1.t().compareTo(f2.t());
^
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Comparable<CAP#1> from capture of ?
CAP#2 extends Comparable<CAP#2> from capture of ?
Note: Some messages have been simplified; recompile with -Xdiags:verbose
to get full output
1 error
Of course the compiler stops this: there's no static guarantee that
f1.t() has same type as f2.t(). They are both some Foo<#CAP>, but of
different captured types (and so, incompatible).
To compensate for this, you can define makeComp as follows:
static <Z extends Comparable<Z>> Comparator<Foo3<?>>
makeComp(Function<Foo3<?>, Z> func) {
return new Comparator<Foo3<?>>() {
public int compare(Foo3<?> f1, Foo3<?> f2) {
return func.apply(f1).compareTo(func.apply(f2));
}
};
}
And then call it like this:
makeComp(Foo3::t)
And this will work (and then fail at runtime with CCE). But here we are
essentially relying on the compiler to capture the result of Foo<?>::t
once, and then generate a function that will keep yielding the same
captured type - hence making the comparator call type-check.
Maurizio
On 17/12/2018 15:26, Maurizio Cimadamore wrote:
> yep - you ended up using raw types; something I thought about, but
> note that this will generate an unchecked warning:
>
> warning: [unchecked] unchecked call to compareTo(T) as a member of the
> raw type Comparable
> return f1.t().compareTo(f2.t());
> ^
> where T is a type-variable:
> T extends Object declared in interface Comparable
>
> So, strictly speaking the class cast exception comes at no surprise here.
>
> But there's no warning in the original code!!! (not suggesting we
> should add warnings here or there, just noting that we have a case of
> heap pollution that goes under the radar).
>
> Maurizio
>
>
> On 17/12/2018 14:53, B. Blaser wrote:
>> On Mon, 17 Dec 2018 at 13:49, Maurizio Cimadamore
>> <maurizio.cimadamore at oracle.com> wrote:
>>> [...]
>>> Challenge: can you find an example which ends up with same behavior
>>> (ClassCastException) that does _not_ involve method references and/or
>>> lambdas?
>>>
>>> Maurizio
>> I like challenges, but I agree this one is hard as it involves a raw
>> 'Foo3' to simulate the capture side-effect as here under.
>>
>> Thoughts?
>> Bernard
>>
>> import java.util.*;
>> import java.util.stream.*;
>> import java.util.function.*;
>>
>> class Foo3<T extends Comparable<T>> {
>>
>> static Foo3<String> S = new Foo3<>("");
>> static Foo3<Integer> I = new Foo3<>(42);
>>
>> private T t;
>>
>> Foo3(T t) {
>> this.t = t;
>> }
>>
>> T t() {
>> return t;
>> }
>>
>> public static void main(String[] args) {
>> List<Foo3<?>> l = new ArrayList<>();
>> l.add(S);
>> l.add(I);
>>
>> Comparator<? super Foo3<?>> comp = new Comparator<Foo3>() {
>> public int compare(Foo3 f1, Foo3 f2) {
>> return f1.t().compareTo(f2.t());
>> }
>> };
>> Collections.sort(l, comp);
>> }
>> }
More information about the compiler-dev
mailing list