Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java cannot compile generic lambda argument unless parameter type is specified

While testing, I upgraded my Junit to 5.0 (Thus replacing some of my assertTrue() methods with the new versions). After doing so, I found one of my tests didn't compile. I reduced the issue down to plain old java with no junit or other dependencies. The result is the following code which will not compile:

  public static void recreate() {
    // This does NOT work
    Recreation.assertTrue(identity((x) -> Boolean.TRUE)); 
    // This DOES work
    Recreation.assertTrue(identity((String x) -> Boolean.TRUE)); 
  }

  private static class Recreation {
    public static void assertTrue(boolean b) {
      System.out.println("boolean argument: " + b);
    }

    // If this method is removed, the code will compile. 
    public static void assertTrue(Supplier<Boolean> booleanSupplier) {
      System.out.println("supplier argument: " + booleanSupplier.toString());
    }
  }

  private static <K> K identity(Function<String, K> function) {
    return function.apply("hello");
  }

As shown in the above example, the code will compile if either of the following are true:

  1. The lambda parameter type is specified

  2. The overloaded assertTrue(Supplier booleanSupplier) method is removed

Is this an issue with type inference/erasure, or what might be going on here?

Build Error:

Error:(10, 35) incompatible types: inference variable K has incompatible bounds
    lower bounds: java.util.function.Supplier<java.lang.Boolean>,java.lang.Object
    lower bounds: java.lang.Boolean

Specs:

openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1, mixed mode, sharing)

OS: Ubuntu 14.04.5 LTS

EDIT: Confirmed the issue exists on Java 8 as well:

java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
exit status 1
Main.java:10: error: incompatible types: inferred type does not conform to upper bound(s)
    Recreation.assertTrue(identity((x) -> Boolean.TRUE));
                                  ^
    inferred: Boolean
    upper bound(s): Supplier<Boolean>,Object
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
like image 727
user2770791 Avatar asked Apr 02 '19 20:04

user2770791


1 Answers

After looking around and reading the Java Language Specification here https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1

I think there are two steps here:

First, the overloading resolution couldn't infer the type of identity((x) -> Boolean.TRUE) because it's implicit lambda, I think it's not taken into account for simplicity sake. Thus, it will widen the parameter search and use public static void assertTrue(Supplier<Boolean> booleanSupplier).

Second, after overloading resolution is done, type inference kicks in. This time it really check the inferred type which is a Boolean, and since it's not compatible with Supplier<Boolean> booleanSupplier, you get the compilation error.

Like previous answer, there are solutions to this,

e.g

Recreation.assertTrue(identity((x) -> () -> Boolean.TRUE));

I found a good explanation here: Java8: ambiguity with lambdas and overloaded methods

like image 68
Bustanil Arifin Avatar answered Sep 30 '22 16:09

Bustanil Arifin