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 ? 
5
  • Looks like a bug to me. Commented Feb 19, 2021 at 14:14
  • "Then why IntelliJ says" are you saying this only happens in intellij, or does it happen with another compiler too? Commented Feb 19, 2021 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). Commented Feb 19, 2021 at 14:18
  • Java 14 on Netbeans, Windows here. Same problem. The compiler error disappears if I typecast the method reference: (Function<String, String>) String::toLowerCase. Commented Feb 19, 2021 at 14:31
  • @AndyTurner well in fact compiler reports error that are "interpreted" by intellij to make them more "readable". Commented Feb 19, 2021 at 14:47

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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…
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 

2 Comments

Good reference, but not exactly the problem since I do not use any generic method reference here. But almost the same problem...
@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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.