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")
.
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?
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);