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?
This is a known type inference problem. As described in JLS 18.5.3:
Here the wildcard type argument gets "instantiated" to its bound
Object
, and becausePredicate<String>
is not a subtype ofPredicate<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.