4

我目前正在使用一个库方法,该方法将具有通用通配符类型的功能接口作为方法参数(特别是在AssertJ库中)。我发现当我传递一个使用除通配符类型参数以外的任何类型的 lambda 方法参数时,我得到一个编译错误。例如,如果方法是,当我调用.RecursiveComparisonAssert.withEqualsForFields​(BiPredicate<?,​?> equals, String... fieldLocations)ObjectsameInstant(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; }
}

尝试将 lambda 传递给Predicate<String>接受 a 的方法会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

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

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

4

1 回答 1

4

这是一个已知的类型推断问题。如JLS 18.5.3所述:

为了确定通配符参数化功能接口的函数类型,我们必须用特定类型“实例化”通配符类型参数。“默认”方法是简单地将通配符替换为其边界,如第 9.8 节中所述,但这会在 lambda 表达式具有与通配符边界不对应的显式参数类型的情况下产生虚假错误。

这里通配符类型参数被“实例化”到它的 bound Object,并且因为Predicate<String>不是 的子类型Predicate<Object>,所以该方法被认为不适用。

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

Predicate<String> myPredicate = WildcardLambda::stringPredicate;
wildcardPredicateInput(myPredicate);
于 2021-06-30T21:13:18.767 回答