微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

对具有无界通配符类型

如何解决对具有无界通配符类型

我目前正在使用一种库方法,该方法将具有通用 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);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。