与 lambda 和功能接口一起使用时无法理解下限

Posted

技术标签:

【中文标题】与 lambda 和功能接口一起使用时无法理解下限【英文标题】:Problems understanding lower bounds when used with lambda and Functional Interface 【发布时间】:2016-05-20 07:07:22 【问题描述】:

在学习 Java8 Streams 时,我遇到了以下代码 sn-p:

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

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

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

<? extends String>,

它不会编译。

我以为我理解了上限和下限的含义,但显然,我遗漏了一些东西。谁能帮助解释为什么下限适用于此 lambda?

【问题讨论】:

extends 也可以编译 -- Predicate&lt;? extends String&gt; predicate = s -&gt; s.startsWith("g") -- 但是,我们不能将 String 提供给这个谓词:) 另请参阅我的 article on wildcard ***.com/questions/33085151/… Predicate&lt;? super String&gt; 确实 not 暗示您可以将Object 传递给它(这样做会产生编译器错误)。签名Predicate&lt;? super String&gt; 只是暗示它可能Predicate&lt;Object&gt;,这没关系,因为该谓词仍然能够处理String 输入。换句话说,Predicate&lt;? super String&gt; 表示此谓词可以使用字符串,而不管其实际类型如何,因此Predicate&lt;String&gt;Predicate&lt;? super String&gt; 的有效实现。 我很抱歉没有提供完整的代码。没有编译的实际代码是之后出现的 boolean test = anyMatch(predicate); 行。实际的谓词行本身确实以上限和下限语法编译。顺理成章地思考了一会儿,我明白了为什么 anyMatch 会有这样的签名。 【参考方案1】:

Lambda 参数类型是准确的,它不能是? super? extends。这由JLS 15.27.3. Type of a Lambda Expression 覆盖。它引入了 ground target type 概念(基本上是 lambda 类型)。除其他外,它声明:

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

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

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

您的 lambda 类型是 Predicate&lt;String&gt;。同理:

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

甚至

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

鉴于 lambda 类型参数是具体的,在此之后应用正常的类型转换规则:Predicate&lt;String&gt;Predicate&lt;? super String&gt;Predicate&lt;? extends String&gt;。所以Predicate&lt;? super String&gt;Predicate&lt;? extends String&gt; 都应该编译。两者实际上都适用于 javac 8u25、8u45、8u71 以及 ecj 3.11.1。

【讨论】:

感谢您的澄清。有界参数的 Lambda 默认行为是我困惑的核心。【参考方案2】:

我刚刚对其进行了测试,作业本身可以编译。改变的是你是否真的可以打电话给predicate.test()

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

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

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

考虑一个具有T 参数的方法。如果是GenericClass&lt;Foo&gt;,那么传递Bar 是错误的——Bar 不是Foo 的子类型。

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

超级: 当您声明 GenericClass&lt;? super Bar&gt; 时,您是在说“我不知道它的泛型类型参数实际上是什么,但它是 Bar 的超类。”现在考虑一下这对方法调用意味着什么。

您知道您实际得到的是GenericClass&lt;Bar&gt;GenericClass&lt;Baz&gt;。考虑一个返回T 的方法。在前一种情况下,它返回Bar。在后者中,Baz。如果它返回Baz,则将该值分配给Bar 变量是错误的。你不知道它是什么,所以你不能在这里安全地假设任何东西。

考虑一个具有T 参数的方法。如果是GenericClass&lt;Bar&gt;,则传递Bar 是合法的。如果是GenericClass&lt;Baz&gt;,那么传递Bar 仍然是合法的,因为BarBaz 的子类型。

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

总而言之:&lt;? extends T&gt; 表示您可以使用通用返回值,但不能使用参数。 &lt;? super T&gt; 表示您可以使用泛型参数但不能使用返回值。 Predicate.test() 有一个泛型参数,所以你需要super

要考虑的另一件事:通配符表示的范围与对象的实际类型参数有关。它们对您可以使用该对象的类型的影响是相反的。上限通配符 (extends) 是您可以为其分配返回值的变量类型的下限。下限通配符 (super) 是可以作为参数传入的类型的上限。 predicate.test(new Object()) 不会编译,因为下限为 String,它将只接受 String 的子类。

【讨论】:

感谢您的洞察力。返回类型/参数摘要使理解它们的用法变得更加容易。

以上是关于与 lambda 和功能接口一起使用时无法理解下限的主要内容,如果未能解决你的问题,请参考以下文章

无法将 python-geoip 与 AWS Lambda 一起使用

Lambda 表达式和函数式接口的关系

接口与继承的理解和问题思考

如何在 Kotlin 中将函数接收器类型与 SAM 接口一起使用

java学习接口,lambda表达式与内部类

Lambda表达式