与lambda和Functional Interface一起使用时理解下限的问题

时间:2016-02-10 04:04:19

标签: java generics lambda java-8 predicate

在研究Java8 Streams时,我遇到了以下代码片段:

Predicate<? super String> predicate = s -> s.startsWith("g");

由于泛型参数是下限,我认为这不会编译。我看到它的方式,如果一个Object是String的超类型,那么传入一个Object类型应该会破坏它,因为Object没有startsWith()函数。但是,我很惊讶地看到它没有任何问题。

此外,当我调整谓词时采取上限:

<? extends String>,

它不会编译。

我以为我理解了上限和下限的含义,但显然,我错过了一些东西。任何人都可以帮助解释为什么下限适用于这个lambda?

2 个答案:

答案 0 :(得分:8)

Lambda参数类型是精确的,不能是? super? extends。这由JLS 15.27.3. Type of a Lambda Expression涵盖。它引入了地面目标类型概念(基本上是lambda类型)。其中包括:

  

如果T是通配符参数化的功能接口类型且lambda表达式是隐式类型的,那么地面目标类型是非通配符参数化§9.9) T。

强调我的。所以基本上当你写

Predicate<? super String> predicate = s -> s.startsWith("g");

您的lambda类型为Predicate<String>。它与:

相同
Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g"));

甚至

Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g"));
Predicate<? super String> predicate = pred;

鉴于lambdas类型参数是具体的,在适用普通类型转换规则之后:Predicate<String>Predicate<? super String>Predicate<? extends String>。因此Predicate<? super String>Predicate<? extends String>都应该编译。这两个实际上都适用于javac 8u25,8u45,8u71以及ecj 3.11.1。

答案 1 :(得分:7)

我刚测试过它,任务本身就编译了。您是否可以实际呼叫predicate.test()

会发生什么变化

让我们退一步,使用占位符GenericClass<T>进行解释。对于类型参数,Foo扩展BarBar扩展Baz

扩展: 当你声明一个GenericClass<? extends Bar>时,你会说“我不知道它的泛型类型参数究竟是什么,但它是Bar的子类。”实际实例将始终具有非通配符类型参数,但在此部分代码中您不知道它的值是什么。现在考虑一下这对方法调用意味着什么。

你知道你实际得到的是GenericClass<Foo>GenericClass<Bar>。考虑一个返回T的方法。在前一种情况下,其返回类型为Foo。在后者中,Bar。无论哪种方式,它都是Bar的子类型,可以安全地分配给Bar变量。

考虑具有T参数的方法。如果是GenericClass<Foo>,则传递Bar是错误的 - Bar不是Foo的子类型。

因此,使用上限可以使用泛型返回值,但不能使用泛型方法参数。

超级: 当你声明一个GenericClass<? super Bar>时,你会说“我不知道它的泛型类型参数究竟是什么,但它是Bar的超类。”现在考虑一下这对方法调用意味着什么。

你知道你实际得到的是GenericClass<Bar>GenericClass<Baz>。考虑一个返回T的方法。在前一种情况下,它返回Bar。在后者中,Baz。如果它返回Baz,则将该值分配给Bar变量是一个错误。你不知道它是哪一个,所以你不能安全地在这里做任何事情。

考虑具有T参数的方法。如果是GenericClass<Bar>,则传递Bar是合法的。如果是GenericClass<Baz>,则传递Bar仍然合法,因为BarBaz的子类型。

因此,使用下限可以使​​用泛型方法参数,但不能使用泛型返回值。

总结:<? extends T>表示您可以使用泛型返回值,但不能使用参数。 <? super T>表示您可以使用通用参数但不能返回值。 Predicate.test()有一个通用参数,因此您需要super

要考虑的另一件事:通配符所声明的边界是关于对象的实际类型参数。它们对该对象一起使用的类型的后果是相反的。上限通配符(extends)是可以为其分配返回值的变量类型的下限。下限通配符(super)是您可以作为参数传递的类型的上限。 predicate.test(new Object())将无法编译,因为String的下限只会接受String的子类。