5

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?

3
  • Something I don't understand: shouldn't BiPredicate take two arguments? I only see you supplying one in stringPredicate. I don't know if this directly affects the error however. Commented Jun 30, 2021 at 19:58
  • @markspace Sorry for the confusion: I started out explaining where I ran into the issue in the wild (which used BiPredicate), then followed up with a simplified version of the same issue (which used Predicate). I'll update the text to make that clearer. Commented Jun 30, 2021 at 20:00
  • I'm going to guess that it's just the way generics work, ? is usually only substitutable for Object. Possibly this looks like a design error in the library, they should have used <X exends Comparable> assert( BiPredicate<? super X, ? super X> or something like that. However I should let an actual expert chime in with a definitive answer. Commented Jun 30, 2021 at 20:10

1 Answer 1

4

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.