Java compilation error using a generic lambda for a method parameter with an unbounded wildcard type

158 Views Asked by At

I'm currently using a library method that takes a functional interface with a generic wildcard type as a method parameter (specifically, RecursiveComparisonAssert.withEqualsForFields​(BiPredicate<?,​?> equals, String... fieldLocations) in the AssertJ library). I discovered that when I pass a lambda method argument that uses any type other than Object for the wildcard type parameter, I get a compilation error. For instance, if the method is sameInstant(Instant i1, Instant i2), I get a compiler error when I call withEqualsForFields(this::sameInstant, "someField").

Simplified Example

As a more simple example of this phenomenon not requiring the use of any particular library to reproduce, take the following scenario using a Predicate<?> method parameter:

public class WildcardLambda {
    public static void main(String[] args) {
        wildcardPredicateInput(WildcardLambda::objectPredicate);
        wildcardPredicateInput(WildcardLambda::stringPredicate); // Fails
        wildcardPredicateInput((Predicate<String>) WildcardLambda::stringPredicate);
        wildcardPredicateInput(input -> stringPredicate(input));

        genericPredicateInput(WildcardLambda::objectPredicate);
        genericPredicateInput(WildcardLambda::stringPredicate);
    }

    private static void wildcardPredicateInput(Predicate<?> predicate) {}
    private static <T> void genericPredicateInput(Predicate<T> predicate) {}

    private static boolean objectPredicate(Object input) { return true; }
    private static boolean stringPredicate(String input) { return true; }
}

Attempting to pass a lambda that would be a Predicate<String> to a method that accepts a Predicate<?> results in a compilation error:

$ javac WildcardLambda.java -Xdiags:verbose
WildcardLambda.java:6: error: method wildcardPredicateInput in class WildcardLambda cannot be applied to given types;
        wildcardPredicateInput(WildcardLambda::stringPredicate); // Fails
        ^
  required: Predicate<?>
  found:    WildcardLa[...]icate
  reason: argument mismatch; invalid method reference
      method stringPredicate in class WildcardLambda cannot be applied to given types
        required: String
        found:    Object
        reason: argument mismatch; Object cannot be converted to String
1 error

However, passing a lambda that is a Predicate<Object> or explicitly casting the lambda to Predicate<String> succeed. Additionally, this works if it is passed to a generic method that expects a Predicate<T>.

Why does this lambda usage result in a compilation error? Is there something I'm overlooking in the JLS that indicates that this should fail to compile?

1

There are 1 best solutions below

0
On BEST ANSWER

This is a known type inference problem. As described in JLS 18.5.3:

In order to determine the function type of a wildcard-parameterized functional interface, we have to "instantiate" the wildcard type arguments with specific types. The "default" approach is to simply replace the wildcards with their bounds, as described in §9.8, but this produces spurious errors in cases where a lambda expression has explicit parameter types that do not correspond to the wildcard bounds.

Here the wildcard type argument gets "instantiated" to its bound Object, and because Predicate<String> is not a subtype of Predicate<Object>, the method is considered not applicable.

Providing the type hint, via a cast (as in the post) or a local variable (as below), solves the problem.

Predicate<String> myPredicate = WildcardLambda::stringPredicate;
wildcardPredicateInput(myPredicate);