2

The following code compiles with javac, and with Eclipse 4.6.1/4.6, but produces an error in Eclipse 4.6.2:

package ecbug;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Foo
{
    class A
    {
        public int getStart() { return 0; }
    }

    void someMethod(List<A> toRemove)
    {
        Collections.sort(toRemove, Comparator.comparing(t -> -t.getStart()));
    }
}

Eclipse 4.6.2 complains, under -t.getStart(), that there is a Type mismatch: cannot convert from int to Comparable<? super Comparable<? super U>>.

I would think that the arguments to Comparator.comparing(...) should be a Comparable<T> with T = A, with functional method compareTo which returns int. Eclipse seems to believe that the lambda function should return Comparable<? super Comparable<? super U>>, however.

I strongly suspect an Eclipse bug, but there are certainly cases where Eclipse has correctly implemented the language specification and javac hasn't, so it seems worth asking: is this an Eclipse bug or a javac bug? Can any language-lawyers out there point out the relevant parts of the language spec?

Possibly related questions, which in my eyes are not duplicates:

Java 8 Stream flatMap and group by code compiler error - similar error message, but unclear if it is exactly the same issue; Answer claims that it is an Eclipse bug but provides no bug link nor JLS quotations; refers to old Eclipse version.

Why didn't this java 8 example using type inference compile in Eclipse? - similar to previous

Java Stream collect after flatMap returns List<Object> instead of List<String> - again, may be a different issue; comments claim Eclipse problem but do not justify via reference to JLS nor provide a link to an Eclipse bug report.

Community
  • 1
  • 1
davmac
  • 20,150
  • 1
  • 40
  • 68
  • I've seen several questions like this over the last couple of years. Did you do a really thorough search? – T.J. Crowder Feb 08 '17 at 12:53
  • @T.J.Crowder it is difficult to find the correct search terms for something so specific, however I did search for a while, yes. There are some questions which may show a related error (eg http://stackoverflow.com/questions/25853988/why-didnt-this-java-8-example-using-type-inference-compile-in-eclipse/25854229) but I can't find any which relate back to the language spec, which is what I have asked for here. – davmac Feb 08 '17 at 12:58
  • Compiles without error for me (Eclipse 4.6) – Thomas Fritsch Feb 08 '17 at 14:01
  • @ThomasFritsch indeed, it appears to have changed with the latest Eclipse JDT: version 3.12.2.v20161124-1400 - which is part of the 4.6.2 update (4.6.1 and 4.6 compile the code successfully). It's still an open question as to which behaviour is correct according to the language spec, though. – davmac Feb 08 '17 at 14:53
  • I have gone ahead and submitted an Eclipse bug - https://bugs.eclipse.org/bugs/show_bug.cgi?id=511924 - to see what the developers say. – davmac Feb 08 '17 at 15:18

1 Answers1

2

Your explanation doesn’t hit it. The argument to Comparator.comparing(...) (single argument version) should not be a Comparable<T>, but rather a Function<? super T,? extends U>, whereas T := A, but U being U extends Comparable<? super U>.

So when you say

Eclipse seems to believe that the lambda function should return Comparable<? super Comparable<? super U>>

you are right regarding Eclipse’s expectation and Eclipse is right in expecting that, too.

But your function returns an int value that is supposed to be compared and when you box that value to Integer, you have a type that fulfills the expected U extends Comparable<? super U> constraint, as Integer implements Comparable<Integer>. In other words, U should be inferred to be Integer, but apparently, this specific Eclipse version fails to do this due to the required boxing of int to Integer.


As a side note, when you want to compare an int property, you might want to use Comparator.comparingInt(...) anyway. With this factory, the returned comparator avoids boxing of int to Integer altogether.

Additionally, you should not reverse an integer ordering by negation. The problem is that
-Integer.MIN_VALUE == Integer.MIN_VALUE, as trying to negate the smallest possible int value causes an overflow evaluating to the smallest int value again, instead of the biggest one. Using negation for reversing the order may work in a lot of situations, in some of them it might be justified as this special value can be precluded, however, it creates a bad habit, that can backfire awfully in a scenario where the situation can occure, of course, only rarely and usually only at the customer…

The correct idiom would be

Collections.sort(toRemove, Comparator.comparingInt(A::getStart).reversed());

which works by swapping the two elements to compare, which is valid in all scenarios and has no performance drawbacks.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Yes, I was mixed up about the argument type to `Comparator.comparing(...)`. This all sounds correct, but I'll wait for a response on the Eclipse bug before I accept; thanks. Regarding use of `comparingInt`, I am aware of this, but the original code from which the test case is derived is not my code. – davmac Feb 09 '17 at 12:54
  • It was marked as a duplicate of another bug - https://bugs.eclipse.org/bugs/show_bug.cgi?id=510111 - the comments there are interesting, eg "... wherever Fi θ is mentioned javac first performs an unspecified substitution: all inference variables that are already instantiated are replaced by their respective instantiation." "... JDK-8052325 mandates that we *not* generate further constraints. The lack of such constraints is what makes the example in this bug fail." (https://bugs.openjdk.java.net/browse/JDK-8052325). It appears there are some spec issues. Accepting your answer regardless. – davmac Feb 09 '17 at 14:45