16

Both calls are correct:

Collectors.groupingBy((String s)->s.toLowerCase(),Collectors.counting());
Collectors.groupingBy((String s)->s.toLowerCase(Locale.ENGLISH),Collectors.counting());

Since then, why the following one is wrong:

Collectors.groupingBy(String::toLowerCase,Collectors.counting());

after all String::toLowerCase can not correspond to the second one... Then why IntelliJ says Reference to 'toLowerCase' is ambiguous, both 'toLowerCase(Locale)' and 'toLowerCase()' match?

String::toLowerCase must be unambiguously resolved to (String s)->s.toLowerCase() or did I miss something?

Of course if I put more context to IntelliJ like:

Collector<String,?,Map<String,Long>> c = Collectors.groupingBy(String::toLowerCase,Collectors.counting());

that is correct, but alas in Java 10 var inference type context it is wrong:

var c = Collectors.groupingBy(String::toLowerCase,Collectors.counting());

I understand that compiler can not infer the input type of counting. If I write:

Collector<String,?,Long> counter = Collectors.counting();
var c = Collectors.groupingBy(String::toLowerCase,counter);

it it correct. Thus again, why compiler is not able to infer the only acceptable form?

-------EDIT--------

I used IntelliJ/compiler interchangeably just because I used IntelliJ first and error reported was :

Reference to 'toLowerCase' is ambiguous, both 'toLowerCase(Locale)' and 'toLowerCase()' match

Compiler's error was much much more unreadable (but contains more hints on why inference fails), something like:

Demo.java:31: error: incompatible types: cannot infer type-variable(s) T#1,K,A,D,CAP#1,T#2
        Collectors.groupingBy(String::toLowerCase,Collectors.counting());
                             ^
    (argument mismatch; invalid method reference
      incompatible types: Object cannot be converted to Locale)
  where T#1,K,A,D,T#2 are type-variables:
    T#1 extends Object declared in method <T#1,K,A,D>groupingBy(Function<? super T#1,? extends K>,Collector<? super T#1,A,D>)
    K extends Object declared in method <T#1,K,A,D>groupingBy(Function<? super T#1,? extends K>,Collector<? super T#1,A,D>)
    A extends Object declared in method <T#1,K,A,D>groupingBy(Function<? super T#1,? extends K>,Collector<? super T#1,A,D>)
    D extends Object declared in method <T#1,K,A,D>groupingBy(Function<? super T#1,? extends K>,Collector<? super T#1,A,D>)
    T#2 extends Object declared in method <T#2>counting()
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • Looks like a bug to me. – M A Feb 19 '21 at 14:14
  • "Then why IntelliJ says" are you saying this only happens in intellij, or does it happen with another compiler too? – Andy Turner Feb 19 '21 at 14:15
  • Same on Eclipse: the compiler seems to infer the type argument of `Collectors.counting()` as `Object`, hence it expects that `String` has a method `toLowerCase(Object)`. – M A Feb 19 '21 at 14:18
  • Java 14 on Netbeans, Windows here. Same problem. The compiler error disappears if I typecast the method reference: `(Function) String::toLowerCase`. – MC Emperor Feb 19 '21 at 14:31
  • @AndyTurner well in fact compiler reports error that are "interpreted" by intellij to make them more "readable". – Jean-Baptiste Yunès Feb 19 '21 at 14:47

2 Answers2

8

This is compiler "weakness", at least until this JEP is in place.

I have already answered almost the same exact question here. There is also another answer from JDK core developers too.

There is also yet another question that is very close to yours.

What matters is that this is known to cause a problem, at times, but has a trivial solution - use a lambda, and thus an explicit type, according to the JLS.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    You don’t need a lambda here, as you normally don’t invoke `Collectors.groupingBy` as a statement nor assign it to a variable. Just use it as an argument to `collect` on a `Stream` and there’s no problem… – Holger Feb 22 '21 at 09:01
1

My guess is that the compiler is finding two occurrences of toLowerCase in the String class, so it decides to first infer from the second argument, Collectors.counting(), which is resolved to Object. This leads the compiler to throw an error because it can't find any toLowerCase() method accepting an Object.

If we try to define a method to use it as replacement:

static String toLowerCase(String s) {
    return s.toLowerCase();
}

the following would then work:

Collectors.groupingBy(Test::toLowerCase, Collectors.counting()); // compiles ok

But if we introduce another overload, the problem appears again:

static String toLowerCase(String s) {
    return s.toLowerCase();
}

static String toLowerCase(String s, Locale locale) {
    return s.toLowerCase(locale);
}

Collectors.groupingBy(Test::toLowerCase,Collectors.counting()); // fails again
M A
  • 71,713
  • 13
  • 134
  • 174
  • Good reference, but not exactly the problem since I do not use any generic method reference here. But almost the same problem... – Jean-Baptiste Yunès Feb 19 '21 at 15:25
  • @Jean-BaptisteYunès Actually you're right it's not exactly the same, because in that reported bug, the `get()` method is not overloaded. But I still think it's a problem due to overloaded method resolution – M A Feb 19 '21 at 16:18