10

Below are the two lines of my code snippet:

List<String> listDevs = Arrays.asList("alvin", "Alchemist", "brutus", "larsen", "jason", "Kevin");

listDevs.sort(Comparator.comparing(String::length)); //This works fine
listDevs.sort(String::compareToIgnoreCase); //This works fine

But (out of expermient) when I try to write

listDevs.sort(Comparator.comparing(String::compareToIgnoreCase));

The compiler throws error

Cannot make a static reference to the non-static method compareToIgnoreCase(String) from the type String

Similar happens to the below code

listDevs.sort(Comparator.comparing(String::compareTo));

I understand the error and that it works fine if I remove the Comparator.comparing (as shown above).

But my point is, how does this line works?

listDevs.sort(Comparator.comparing(String::length));

I believe I am missing something. I have read this thread. Is this the same scenario?

Community
  • 1
  • 1
Vikrant
  • 1,809
  • 1
  • 13
  • 18
  • 1
    I'd say `String::compareToIgnoreCase` simply does not match the method signature expected by `Comparator.comparing`. It expects `String -> Comparable` but gets `(String, String) -> Integer`. Not sure about the error, though. Maybe Java tries to interpret the non-matching method as a static method to see whether that matches... BTW, in Eclipse I get a different, more sensible error message. – tobias_k Apr 07 '17 at 09:25

3 Answers3

12

Comparator.comparing expects a Function which describes a comparable property of the elements. So String::length is sufficient as length() is a property of the String evaluating a String to an int (that’s why comparingInt is preferable here).

In contrast, String.compareToIgnoreCase and String.compareTo are comparison methods. They compare two String objects. So references to them are sufficient where a Comparator is expected, but not where a property Function is expected.

It’s like you have a factory saying “Gimme an engine, and we build a car for you” and you are trying to give them a complete car. While that existing car is valid where a car is expected, there is no sense in passing it to the factory to built a car.

Unfortunately, the current compiler implementation is very bad at reporting error with functional signatures. You will almost always see messages like “Cannot make a static reference to the non-static method …” when signatures mismatch.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks for your inputs. I understand your point but still not convinced. Comparator.comparing works on keyExtractors where an integer is the input and String is the output. If a method returns an integer, there shouldn't be any problem. The return types of compareTo and length methods are exactly the same. So if we talk, syntactically, there should not be any problem for the comparing method to accept both the methods. And, we agree that the error message is misleading. – Vikrant Apr 07 '17 at 10:14
  • 2
    No, the string is the input and the integer is the output, not vice versa. This is the case for `length()`, where the input is the string you call this method on, but not for `compareTo(String)` et al, which you call on a string and pass a *second* string as parameter. When using unbound method references to instance methods, you have to learn to count the receiver instance as function parameter. Otherwise, the parameter-less method `length()` wouldn’t work either. – Holger Apr 07 '17 at 10:18
  • My bad. I was meaning otherwise. I meant the key extractor uses the String to generate a (integer) key based on which the sorting takes place. But this is the same case with length() where the integer is the factor for sorting, making both cases similar. – Vikrant Apr 07 '17 at 10:24
  • 3
    `length()` has no parameters and and `compareTo(String)` has a parameter. And both are instance methods. So they are not similar, but entirely different. – Holger Apr 07 '17 at 10:25
  • @Vikrant Holger is right, you can see the first section of [my answer](http://stackoverflow.com/questions/43274306/comparator-comparing-throwing-non-static-reference-exception-while-taking-s/43274621#43274621) then you know why Holger say they are not similar. – holi-java Apr 08 '17 at 03:55
8

The sort method expected a Comparator.

When you do this, you are indeed providing one.

 listDevs.sort(Comparator.comparing(String::length));

Same happens here(but a bit non-intuitive):

 listDevs.sort(String::compareToIgnoreCase)
 listDevs.sort((left, right) -> left.compareToIgnoreCase(right)); // same thing as above

That's exactly the definition of a Comparator - take two Strings and return an int.

The line that you say how come this works: listDevs.sort(Comparator.comparing(String::length)); is actually pretty simple.

Comparator.comparing takes a Function that transforms your input type into something that is Comparable. In your case takes a String and returns an Integer; which is Comparable.

Eugene
  • 117,005
  • 15
  • 201
  • 306
5

JLS says Compile-Time Declaration of a Method Reference of ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways.

Given a targeted function type with n parameters, a set of potentially applicable methods is identified:

ReferenceType :: [TypeArguments] Identifier has two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.

A method reference expression of the form ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways. If Identifier refers to an instance method, then the implicit lambda expression has an extra parameter with type of this compared to if Identifier refers to a static method. It is possible for ReferenceType to have both kinds of applicable methods, so the search algorithm described above identifies them separately, since there are different parameter types for each case.

Comparator.comparing method accept a Function<T,R extends Comparable<? super R>>. when you use String::compareToIgnoreCase that will reports error,because it has two parameters one is implicit this another is a comparing string of method parameter, so it is more like a BiFunction<String,String,Integer> not a Function<String,Integer>.

BiFunction<String, String, Integer> comparator = String::compareToIgnoreCase;
// you can't assign a BiFunction to a Function
// because one is incompatiable with another.
Function<String,Integer> function = comparator;

Stream.sort method accept a Comparator, and Comparator is more like a BiFunction<T,T,Integer> so it is compatiable with String::compareToIgnoreCase. on the other hand, they can be interchangeable. for example:

Comparator<String> primary = String::compareToIgnoreCase;
BiFunction<String, String, Integer> comparator1 = primary::compare;
Comparator<String> comparator2 = comparator1::apply;

you can using comparing(String::toLowerCase) instead, it is equalivent to String::compareToIgnoreCase, for example:

// String::compareToIgnoreCase
listDevs.sort(String::compareToIgnoreCase); 

// comparing(String::toLowerCase)
listDevs.sort(comparing(String::toLowerCase))
Community
  • 1
  • 1
holi-java
  • 29,655
  • 7
  • 72
  • 83