为什么Java在使用泛型与lambda时选择Object参数化类型?

时间:2014-12-25 14:11:23

标签: java generics lambda java-8

让我说我有一个方法,它接受java.util.function.Predicate并返回CompletableFuture:

public <R> CompletableFuture<R> call(Predicate<R> request) {
    return new CompletableFuture<>();
}

如果我使用这样的匿名类调用此方法:

Integer a = call(new Predicate<Integer>() {
    @Override
    public boolean test(Integer o) {
        return false;
    }
}).join();

它有效,因为我必须明确声明谓词的类型。但是,如果我使用lambda表达式而不是像这样的匿名类:

Integer a = call(o -> false).join();

它没有编译,因为Java认为它是Predicate<Object>并返回如下消息:

Error:(126, 42) java: incompatible types: java.lang.Object cannot be converted to java.lang.Integer

我找到了一些解决方法。我可以显式创建CompletableFuture变量而不是链接,或添加一个不必要的额外参数Class<R>,它告诉Java我们想要获取哪种类型或强制lambda表达式中的类型。

但是我想知道为什么Java在第一个lambda示例中选择Object而不是Integer,它已经知道我想要哪种类型,因此编译器可以使用最具体的类型而不是{{1因为所有这三个变通办法对我来说都很难看。

2 个答案:

答案 0 :(得分:4)

Java 8的类型推断存在限制,它不会查看您为其分配结果的变量的类型。相反,它会为类型参数推断类型Object

您可以通过在lambda中明确指定参数o的类型来修复它:

Integer a = call((Integer o) -> false).join();

答案 1 :(得分:3)

其他人已经回答了如何将lambda强制为正确的类型。但是,我认为Predicate<Object>不是&#34;错误&#34;对于该谓词,你应该被允许使用它 - 你有一个所有对象的谓词 - 它应该适用于所有对象,包括Integer s;为什么你需要在Integer s上制作一个更受限制的谓词?你不应该。

我认为真正的问题是你的call方法的签名。它太严格了。 Predicate是其类型参数的使用者(它只有一个采用其类型参数类型并返回boolean)的方法;因此,根据PECS规则(生产者extends,消费者super),您应始终将其与super有界通配符一起使用。

所以你应该声明你的方法:

public <R> CompletableFuture<R> call(Predicate<? super R> request)

无论您使用此方法中的代码,在将绑定更改为super后仍会编译,因为Predicate是使用者。现在,您不需要强制它Predicate<Integer> - 您可以使用Predicate<Object>并仍然让它返回CompletableFuture<Integer>