问题描述
我目前正在使用一种库方法,该方法将具有通用 functional interface 类型的 wildcard 作为方法参数(特别是 RecursiveComparisonAssert.withEqualsForFields(BiPredicate<?,?> equals,String... fieldLocations)
库中的 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中所述:
为了确定通配符参数化函数接口的函数类型,我们必须使用特定类型“实例化”通配符类型参数。 “默认”方法是简单地用它们的边界替换通配符,如第 9.8 节所述,但在 lambda 表达式具有与通配符边界不对应的显式参数类型的情况下,这会产生虚假错误。
这里通配符类型参数被“实例化”到它的边界 Object
,并且因为 Predicate<String>
不是 Predicate<Object>
的子类型,该方法被认为不适用。
通过强制转换(如帖子中所示)或局部变量(如下所示)提供类型提示可以解决问题。
Predicate<String> myPredicate = WildcardLambda::stringPredicate;
wildcardPredicateInput(myPredicate);