Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java var and inference type ambiguity

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 ?
like image 607
Jean-Baptiste Yunès Avatar asked Feb 19 '21 13:02

Jean-Baptiste Yunès


People also ask

What is local variable type inference in Java?

Local Variable Type Inference is one of the most evident change to language available from Java 10 onwards. It allows to define a variable using var and without specifying the type of it. The compiler infers the type of the variable using the value provided. This type inference is restricted to local variables. Old way of declaring local variable.

How to use VAR for type inference in Java 10?

In Java 10, using the var reserved word for type inference was forbidden when declaring the parameter list of implicitly typed lambda expressions. Now, we can do things like this: The expressed goal of this feature is very simple: allow var to be used to declare the formal parameters of an implicitly typed lambda expression.

What are the ambiguities in Java?

The ambiguities are those issues that are not defined clearly in the Java language specification. The different results produced by different compilers on several example programs support our observations. Here we will be discussing in the following order. Attention reader!

What is type inference in JavaScript?

Type inference is used in var keyword in which it detects automatically the datatype of a variable based on the surrounding context. The below examples explain where var is used and also where you can’t use it.


2 Answers

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.

like image 168
Eugene Avatar answered Sep 30 '22 04:09

Eugene


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
like image 30
M A Avatar answered Sep 30 '22 04:09

M A