与 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<? extends String> predicate = s -> s.startsWith("g")
-- 但是,我们不能将 String
提供给这个谓词:) 另请参阅我的 article on wildcard
***.com/questions/33085151/…
Predicate<? super String>
确实 not 暗示您可以将Object
传递给它(这样做会产生编译器错误)。签名Predicate<? super String>
只是暗示它可能是Predicate<Object>
,这没关系,因为该谓词仍然能够处理String
输入。换句话说,Predicate<? super String>
表示此谓词可以使用字符串,而不管其实际类型如何,因此Predicate<String>
是Predicate<? super String>
的有效实现。
我很抱歉没有提供完整的代码。没有编译的实际代码是之后出现的 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<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;
鉴于 lambda 类型参数是具体的,在此之后应用正常的类型转换规则:Predicate<String>
是 Predicate<? super String>
或 Predicate<? extends String>
。所以Predicate<? super String>
和Predicate<? extends String>
都应该编译。两者实际上都适用于 javac 8u25、8u45、8u71 以及 ecj 3.11.1。
【讨论】:
感谢您的澄清。有界参数的 Lambda 默认行为是我困惑的核心。【参考方案2】:我刚刚对其进行了测试,作业本身可以编译。改变的是你是否真的可以打电话给predicate.test()
。
让我们退后一步,使用占位符GenericClass<T>
进行解释。对于类型参数,Foo
扩展 Bar
和 Bar
扩展 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
仍然是合法的,因为Bar
是Baz
的子类型。
因此,有了下限,您可以使用泛型方法参数,但不能使用泛型返回值。
总而言之:<? extends T>
表示您可以使用通用返回值,但不能使用参数。 <? super T>
表示您可以使用泛型参数但不能使用返回值。 Predicate.test()
有一个泛型参数,所以你需要super
。
要考虑的另一件事:通配符表示的范围与对象的实际类型参数有关。它们对您可以使用该对象的类型的影响是相反的。上限通配符 (extends
) 是您可以为其分配返回值的变量类型的下限。下限通配符 (super
) 是可以作为参数传入的类型的上限。 predicate.test(new Object())
不会编译,因为下限为 String
,它将只接受 String
的子类。
【讨论】:
感谢您的洞察力。返回类型/参数摘要使理解它们的用法变得更加容易。以上是关于与 lambda 和功能接口一起使用时无法理解下限的主要内容,如果未能解决你的问题,请参考以下文章
无法将 python-geoip 与 AWS Lambda 一起使用