0

I'm deeply confused by why the first block of code can compile, while the second block of code can't compile.

Can compile

        int[][] arr = {{2,2}, {1,2}, {3,4}};
        List<int[]> list = Arrays.asList(arr);
        
        // sort by first element
        list.sort(Comparator.comparingInt(x -> x[0]));

Cannot compile. Error is "Array type expected; found: 'java.lang.Object'"

        int[][] arr = {{2,2}, {1,2}, {3,4}};
        List<int[]> list = Arrays.asList(arr);

        // sort by first element, then by second element
        list.sort(Comparator.comparingInt(x -> x[0]).thenComparingInt( x -> x[1]));

In order to fix the error, I had to do

list.sort(Comparator.comparingInt(x -> ((int[])x)[0]).thenComparingInt( x -> ((int[])x)[1]));

My question is, why I had to typecast if calling comparingInt and then thenComparingInt, while typecast is not needed if I was only calling comparingInt?

rigby
  • 13
  • 4
  • 1
    Does this answer your question? [Very confused by Java 8 Comparator type inference](https://stackoverflow.com/questions/24436871/very-confused-by-java-8-comparator-type-inference) – Alexander Ivanchenko Mar 26 '22 at 19:21

1 Answers1

2

With just Comparator.comparingInt, the compiler can infer that the single generic method call and the desired type should be the same - Comparator<int[]>.

The second chained call disrupts the type inference completely. The desired type is still Comparator<int[]>, and this is what thenComparingInt should return. But the compiler is not (yet?) smart enough to determine what the return type of Comparator.comparingInt should be. That's why you get a compiler error.

Your casts work, but you can make it a bit easier by telling the compiler what generic type to use for Comparator.comparingInt. Then, thenComparingInt continues from that generic type.

There are two ways I can think of:

  1. list.sort(Comparator.<int[]>comparingInt(x -> x[0]).thenComparingInt(x -> x[1]))

  2. list.sort(Comparator.comparingInt((int[] x) -> x[0]).thenComparingInt(x -> x[1]))

In the first case, you just tell the compiler "the generic type for the method is int[]". In the second case, you let the compiler infer it from the argument, which you tell the compiler is a Function<int[], int> by specifying the input type.

Rob Spoor
  • 6,186
  • 1
  • 19
  • 20
  • 1
    Nice answer. In the first case, it is referred to as `type witnessing`. – WJS Mar 26 '22 at 19:34
  • Thanks, I don't remember hearing this term before. – Rob Spoor Mar 26 '22 at 19:35
  • 1
    You might find this useful https://stackoverflow.com/questions/24932177/type-witness-in-java-generics. Personally, on some occasions, I still have problems getting it to work (especially using `thenComparing`). That's when I use your second method. – WJS Mar 26 '22 at 19:54