Java 8 Lambda表达式和一等值

30
Java 8的闭包真的是一流值(first-class values)吗?还是只是语法糖?

这个常见问题解答对于关于这个主题的问题非常有用 - http://www.lambdafaq.org/are-lambda-expressions-objects/ - Matt
1
它们绝对不是语法糖;请参见 Stuart Marks 在此问题中的回答:http://programmers.stackexchange.com/questions/177879/type-inference-in-java-8 - Maurice Naftalin
我不想在这个话题上进行辩论,但是有很多信息可用于Lambda项目页面,描述了JDK的确切实现,这也涵盖了虚拟扩展方法。 - Brett Ryan
@MauriceNaftalin - Stuart Marks并没有说它们不是语法糖。他说它们“不是匿名内部类的语法糖”。非常不同。 - Graham Lea
@GrahamLea对这个问题的回答在下面的第一行开始就写着:“我认为Java 8闭包(“Lambdas”)既不仅是纯粹的语法糖,也不是第一类值”。此外,除了aic之外,它们还可以用于什么其他方面? - Maurice Naftalin
@MauriceNaftalin 您说得对。那天我一定是瞎了眼。谢谢。 - Graham Lea
5个回答

60
我认为Java 8的闭包(“Lambdas”)既不仅仅是语法糖,也不是一级值。
我在另一个StackExchange问题的answer中解决了语法糖的问题。
至于Lambda是否“一级”,这实际上取决于您的定义,但我会提出一个论点,即Lambda实际上并不是一级的。
某种意义上,Lambda想要成为一个函数,但Java 8没有添加函数类型。相反,Lambda表达式被转换为功能接口的实例。这使得可以通过对Java类型系统进行轻微更改来将Lambda添加到Java 8中。转换后,结果是一个引用,就像任何其他引用类型一样。事实上,在使用Lambda时(例如,在将Lambda表达式作为参数传递给方法中),与通过接口调用方法无异。接收功能接口类型参数的方法无法确定是传递了Lambda表达式还是实现该功能接口的某个类的实例。

关于lambda是否为对象的更多信息,请参阅此问题的Lambda FAQ Answer

考虑到lambda被转换为对象,它们继承(字面上)所有对象的特性。特别地,对象:

  • 有各种方法,如equalsgetClasshashCodenotifytoStringwait
  • 有一个身份哈希码
  • 可以通过synchronized块进行锁定
  • 可以使用==!=instanceof运算符进行比较

等等。实际上,所有这些都与lambda的预期用途无关。它们的行为本质上是未定义的。您可以编写使用其中任何一个的程序,并且将获得某些结果,但结果可能会因版本不同而有所不同(甚至在不同的运行中也可能如此!)。

简言之,在Java中,对象具有身份,但值(特别是函数值,如果它们存在的话)不应具有任何身份概念。Java 8没有函数类型。相反,lambda表达式被转换为对象,因此它们具有许多与函数无关的负担,特别是身份。这对我来说似乎不太像“一等公民”。
更新2013-10-24
自从几个月前发布我的答案以来,我一直在进一步思考这个问题。从技术上讲,我上面写的一切都是正确的。结论可能更精确地表述为Java 8 lambdas不是纯的(而不是一等级)值,因为它们带有很多对象负担。然而,仅仅因为它们是不纯的并不意味着它们不是一等公民。考虑Wikipedia definition一等级函数的定义。简要地说,将函数视为一等公民的标准包括:
  • 将函数作为参数传递给其他函数
  • 从其他函数返回函数
  • 将函数分配给变量
  • 将函数存储在数据结构中
  • 使函数匿名

Java 8的lambda表达式符合所有这些标准。因此,它们似乎是一流的。

文章还提到函数名称没有特殊状态,而函数名称只是一个类型为函数类型的变量。Java 8的lambda表达式不符合最后一个标准。Java 8没有函数类型;它有功能接口。这些接口有效地像函数类型一样使用,但它们根本不是函数类型。如果您有一个类型为功能接口的引用,您不知道它是lambda表达式、匿名内部类的实例还是恰好实现该接口的具体类的实例。

总之,Java 8的lambda表达式比我最初想象的更像一流的函数。它们只是不是纯粹的一流函数。


2
这取决于你对语法糖的定义。对我来说,语法糖是指编译器为你重写了本可以自己编写的内容。如果你将lambda表达式写成匿名内部类,后者的语义确实会有所不同:它是一个真正的子类,总是创建一个新实例,可以添加字段,可以添加/覆盖方法等。这些不适用于lambda表达式。 - Stuart Marks
1
Cyrille Ka基本上是正确的。为了进一步澄清,我要说“转换”与“重写”是不同的概念。我使用“转换”来表示类型转换的意思。例如,如果我们有Object o =“Hello”,则String文字“Hello”将被转换为类型为Object的引用。另一方面,“重写”是将简单语法转换为更复杂的语法,你自己也可以编写。转换和重写是不同的概念。 - Stuart Marks
1
很有道理。"一流"的概念相当主观。装箱/拆箱有助于给值类型带来幻觉,但不幸的是,在某些时候,底层的原始/引用二分法会显露出来。例如,考虑使用<===比较两个装箱的整数。但总的来说,我最近也在更多地思考这个话题,我会尽快更新我的答案。 - Stuart Marks
1
声明一个泛型函数式接口类型的值。例如:Function<String,String> fn = s -> s.toUpperCase(); - Stuart Marks
1
没错,方法还是方法。如果你想要模拟其中的一个,你需要做一些像创建动态代理这样的事情,这也是我认为模拟框架所做的。 - Stuart Marks
显示剩余10条评论

6

是的,它们是一等值(或者说将会在Java 8发布后成为一等值...)

这意味着您可以将它们作为参数传递,组合它们以创建高阶函数,将它们存储在数据结构中等等。您将能够将它们用于广泛的函数式编程技术。

此外,了解更多关于在这种情况下“一等”意味着什么的定义,请参见:


2
依我看,它是一种语法糖,但同时结合了类型推断、一个新的java.util.functions包和内部类的语义,使其表现为一等值。

1
一个具有对外部上下文进行变量绑定的真正闭包会带来一些开销。我认为Java 8的实现是最优的,足够纯净。至少它不仅仅是语法糖。而且我不知道是否还有更优的实现。

0

对我来说,Java 8中的Lambda只是一种语法糖,因为你不能将其用作第一类公民(http://en.wikipedia.org/wiki/First-class_function),每个函数都应该被包装成对象,这会在与具有纯第一类函数的语言(如SCALA)进行比较时带来许多限制。Java 8闭包只能捕获不可变(“有效最终”)的非本地变量。

这里有更好的解释,为什么它是语法糖 Java Lambdas and Closures


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接