Java 对具有无限通配符类型的方法参数使用通用 lambda 时出现编译错误

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

我目前正在使用采用 functional interface with a generic wildcard type as a method parameter (specifically, RecursiveComparisonAssert.withEqualsForFields​(BiPredicate<?,​?> equals, String... fieldLocations) in the AssertJ 库的库方法。我发现当我传递一个使用 Object 以外的任何类型作为通配符类型参数的 lambda 方法参数时,我会收到编译错误。例如,如果方法是 sameInstant(Instant i1, Instant i2),我在调用 withEqualsForFields(this::sameInstant, "someField").

时会遇到编译器错误

简化示例

作为这种现象的一个更简单的例子,不需要使用任何特定的库来重现,请使用 Predicate<?> 方法参数进行以下场景:

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; }
}

尝试将 Predicate<String> 的 lambda 传递给接受 Predicate<?> 的方法会导致编译错误:

$ 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

但是,传递 Predicate<Object> 的 lambda 或将 lambda 显式转换为 Predicate<String> 会成功。此外,如果将其传递给需要 Predicate<T>.

的通用方法,则此方法有效

为什么这个 lambda 用法会导致编译错误?我在 JLS 中是否忽略了表明这应该无法编译的内容?

这是一个已知的类型推断问题。如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.

此处通配符类型参数“实例化”到其绑定 Object,并且由于 Predicate<String> 不是 Predicate<Object> 的子类型,因此该方法被认为不适用。

通过强制转换(如 post 中)或局部变量(如下所示)提供类型提示可解决问题。

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