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