Java var and inference type ambiguity
Asked Answered
P

2

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

Lowgrade answered 19/2, 2021 at 17:15 Comment(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…Toccaratoccata
N
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
Nepali answered 19/2, 2021 at 15:1 Comment(2)
Good reference, but not exactly the problem since I do not use any generic method reference here. But almost the same problem...Skutchan
@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 resolutionNepali

© 2022 - 2024 — McMap. All rights reserved.