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

M. *_*tin 5 java generics lambda compiler-errors unbounded-wildcard

我当前正在使用一个库方法,该方法采用带有通用通配符类型的功能接口作为方法参数(具体来说,在AssertJ库中)。我发现,当我传递使用通配符类型参数以外的任何类型的 lambda 方法参数时,我会收到编译错误。例如,如果该方法是,则当我调用 时会出现编译器错误。RecursiveComparisonAssert.withEqualsForFields\xe2\x80\x8b(BiPredicate<?,\xe2\x80\x8b?> equals, String... fieldLocations)ObjectsameInstant(Instant i1, Instant i2)withEqualsForFields(this::sameInstant, "someField")

\n

简化示例

\n

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

\n
public class WildcardLambda {\n    public static void main(String[] args) {\n        wildcardPredicateInput(WildcardLambda::objectPredicate);\n        wildcardPredicateInput(WildcardLambda::stringPredicate); // Fails\n        wildcardPredicateInput((Predicate<String>) WildcardLambda::stringPredicate);\n        wildcardPredicateInput(input -> stringPredicate(input));\n\n        genericPredicateInput(WildcardLambda::objectPredicate);\n        genericPredicateInput(WildcardLambda::stringPredicate);\n    }\n\n    private static void wildcardPredicateInput(Predicate<?> predicate) {}\n    private static <T> void genericPredicateInput(Predicate<T> predicate) {}\n\n    private static boolean objectPredicate(Object input) { return true; }\n    private static boolean stringPredicate(String input) { return true; }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
$ javac WildcardLambda.java -Xdiags:verbose\nWildcardLambda.java:6: error: method wildcardPredicateInput in class WildcardLambda cannot be applied to given types;\n        wildcardPredicateInput(WildcardLambda::stringPredicate); // Fails\n        ^\n  required: Predicate<?>\n  found:    WildcardLa[...]icate\n  reason: argument mismatch; invalid method reference\n      method stringPredicate in class WildcardLambda cannot be applied to given types\n        required: String\n        found:    Object\n        reason: argument mismatch; Object cannot be converted to String\n1 error\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

小智 4

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

\n
\n

为了确定通配符参数化函数接口的函数类型,我们必须用特定类型“实例化”通配符类型参数。“默认”方法是简单地将通配符替换为其边界,如 \xc2\xa79.8 中所述,但如果 lambda 表达式具有与通配符边界不对应的显式参数类型,则会产生虚假错误。

\n
\n

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

\n

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

\n
Predicate<String> myPredicate = WildcardLambda::stringPredicate;\nwildcardPredicateInput(myPredicate);\n
Run Code Online (Sandbox Code Playgroud)\n