Java 8 支持闭包吗?
Posted
技术标签:
【中文标题】Java 8 支持闭包吗?【英文标题】:Does Java 8 Support Closures? 【发布时间】:2013-06-16 18:10:31 【问题描述】:我很困惑。我认为 Java 8 将从石器时代出现并开始支持 lambdas/closures。但是当我尝试这个时:
public static void main(String[] args)
int number = 5;
ObjectCallback callback = () ->
return (number = number + 1);
;
Object result = callback.Callback();
System.out.println(result);
...它说number should be effectively final
。那是呃,我认为不是关闭。这听起来就像是通过值而不是通过引用来复制环境。
额外问题!
android 会支持 Java 8 功能吗?
【问题讨论】:
docs.oracle.com/javase/tutorial/java/javaOO/…看这个文档 为什么,为什么,Java。为什么哦为什么。 他们可以保持自己的精神错乱。我也不会错过 var 参数。 lambda 不是闭包,它只是一个匿名函数。闭包是使用创建它的上下文的函数(匿名或非匿名)。请参阅***.com/questions/220658/…。如果您想从 Java 中受益,您可以使用 Scala 或 Groovy。 Java 8 支持 lambdas docs.oracle.com/javase/tutorial/java/javaOO/…,而不是闭包 这取决于你认为闭包是什么。在解释型、有效的单线程语言中,这种可变捕获非常有意义,因为您实际存储的实际上是指向相关环境/符号表的指针。这不适用于高度多线程的编译语言。问问自己——在 JVM 字节码术语中,甚至 mean 会捕获一个可变变量吗?你怎么能代表它? 【参考方案1】:为什么,为什么,Java。为什么哦为什么。
您需要与相关的 Oracle Java 团队成员进行长时间的(私下)讨论,以获得真正的答案。 (如果他们愿意和你谈谈……)
但我怀疑这是向后兼容性和项目资源限制的结合。从务实的角度来看,当前的方法“足够好”。
将过程上下文实现为一等对象(即闭包)要求某些局部变量的生命周期超出声明方法调用的返回。这意味着您不能只是将它们放在堆栈上。相反,您最终会遇到 some 局部变量必须是堆对象的字段的情况。这意味着您需要一种新的隐藏类或对 JVM 架构进行根本性更改。
虽然在技术上实现这种东西是可行的,但 Java 语言并不是一种“绿地”语言。在 Java 中支持“真正的闭包”需要改变性质是很困难的:
Oracle 和第 3 方实施者需要付出巨大努力才能更新所有工具链。 (而且我们不仅仅是在谈论编译器。还有调试器、分析器、混淆器、字节码工程框架、持久性框架......)
然后,其中一些更改可能会影响数百万现有已部署 Java 应用程序的向后兼容性。
对于以某种方式利用 JVM 的其他语言等存在潜在影响。例如,Android 依赖 JVM 架构/字节码文件作为其 Davlik 工具链的“输入语言”。有 Python、Ruby 和为 JVM 平台生成代码的各种功能语言的语言实现。
简而言之,Java 中的“真正的闭包”对于所有相关人员来说都是一个可怕的命题。 “决赛的闭幕式”黑客是一种务实的妥协,确实有效,而且在实践中已经足够好了。
最后,final
限制总是有可能在未来的版本中被删除。 (虽然我不会屏住呼吸....)
android 会支持 Java-8 功能吗?
除非有人拥有可靠的内部知识,否则这是不可能回答的。如果他们这样做了,他们会疯狂地在这里透露它。当然,Google 还没有宣布支持 Java 8。
但好消息是,KitKat 和相应版本的 Android Studio 或 Eclipse ADT 现在支持 Java 7 语法扩展。
【讨论】:
“C# 可以做到...” - 当支持闭包时,C#/CLR 仍然是一个几乎是新领域的语言/平台。令人惊讶的是如果您不必担心破坏现有代码,您可以做到这一点,您拥有大量资源......并且您可以从其他人的错误中学习。 重新开发语言并进行重大更改,这就是 C# 已具体化泛型而 Java 没有的原因。 ;-) @ChrisJester-Young - 是的。并支持尾调用优化和其他一些东西。 @StephenC 好帖子。但有趣的是,Groovy 和 Scala 等 JVM 语言提供了 scala。 @MoreThanFive - 是的......但它们没有与 Java 相同的(业务)约束。【参考方案2】:您必须说明您对“关闭”的定义。
对我来说,“闭包”是从其封闭范围捕获(“关闭”)局部变量的东西(一个函数或对象或其他可以以某种方式运行的东西,例如拥有方法),它可以在其代码中使用该变量,即使函数或对象的方法稍后运行,包括封闭范围不再存在时。在不同的语言中,可以通过值或引用或两者来捕获变量。
根据这个定义,Java 匿名类(自 Java 1.1 以来一直存在)是闭包,因为它们可以从其封闭范围引用局部变量。
Java 8 中的 Lambda 基本上是匿名类的一种特殊情况(即,一个匿名类实现了一个只有一个方法的接口(“函数式接口”),它没有实例变量,并且不引用本身(显式或隐式使用this
))。任何 lambda 都可以重写为等效的匿名类表达式。所以上面所说的也适用于 lambdas。
那是呃,我认为不是结束。
好吧,先生,您对“关闭”的定义搞砸了。
【讨论】:
根据定义,接受命令行参数的编译程序将是一个闭包... “...一直来自 Java 的第一个版本”是不正确的,因为 Java 的第一个版本没有内部类。它们是在 Java 1.1 中引入的,因此在这方面,Java 中从 1.1 开始就存在闭包...... 您对“关闭”的定义是“在匿名类构造函数中按值复制,并且永远不要在关闭中访问它”? @doug65536:匿名类不能声明构造函数。另外,我不知道“永远不要在闭包中访问它”是什么意思——只有在匿名类/lambda 中实际使用的外部变量才会被捕获。捕获的变量不会传递给任何构造函数。是的,在创建匿名类/lambda 时,捕获的变量是按值复制的。 @newacct Java 通过将 final 变量复制到匿名类的实例中来伪造闭包——我将其表示为“在构造函数中”完成(程序员未声明)。您不能在外部范围内修改原始变量,这就是为什么它们必须是最终的。实际上,java 根本没有闭包。除非他们在一种真正支持它们的语言中使用了真正的闭包,否则人们可能不会注意到差异。【参考方案3】:您可以使用最终引用来绕过更改在外部范围中声明的变量的状态,但结果保持不变,闭包外部范围的状态不会保留,并且对引用的对象的进一步更改(通过最终参考)在闭包中看到。
@Test
public void clojureStateSnapshotTest()
Function wrapperFunc;
wrapperFunc = (a) ->
// final reference
final WrapLong outerScopeState = new WrapLong();
outerScopeState.aLong = System.currentTimeMillis();
System.out.println("outer scope state BEFORE: " + outerScopeState.aLong);
Function closure = (b) ->
System.out.println("closure: " + outerScopeState.aLong);
return b;
;
outerScopeState.aLong = System.currentTimeMillis();
System.out.println("outer scope state AFTER: " + outerScopeState.aLong);
// show correct snapshot state
closure.apply(new Object());
return a;
;
// init clojure
wrapperFunc.apply(new Object());
public class WrapLong
public long aLong = 0;
但仍然很有趣...
【讨论】:
【参考方案4】:我认为final
限制有技术原因。 lambda 表达式只是从周围的方法上下文中获取值,因为引用存在于堆栈中,并且不会在方法完成后继续存在。
如果你将上下文的值放入一个引用中,你可以构建一个“真正的”闭包:
import java.util.function.Supplier;
public class CreatingAClosure
public static void main(String[] args)
Supplier<Supplier<String>> mutterfunktion = () ->
int container[] = 0;
return () ->
container[0]++;
return "Ich esse " + container[0] + " Kuchen.";
;
;
Supplier<String> essen = mutterfunktion.get();
System.out.println(essen.get());
System.out.println(essen.get());
System.out.println(essen.get());
奥斯加贝:
Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 3 Kuchen.
您可以获取任何对象的任何合适实例,而不是数组,因为它位于堆上,并且只有对该实例的引用(最终)保留在 lambda 表达式中。
在这种情况下,container
的值包含在 mutterfunktion
中。每次调用 mutterfunktion
都会创建一个新的引用实例。
无法从函数外部访问该值(在 Java 7 及之前的版本中很难构建)。由于 lambda 表达式是作为方法引用实现的,因此本示例中不涉及内部类。
您还可以在方法的上下文中定义container
,您将能够在 lambda 之外进行更改:
public static void main(String[] args)
int container[] = 0;
Supplier<String> essen = () ->
container[0]++;
return "Ich esse " + container[0] + " Kuchen.";
;
System.out.println(essen.get());
System.out.println(essen.get());
container[0]++;
System.out.println(essen.get());
奥斯加贝:
Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 4 Kuchen.
所以你的问题的答案是“是”。
【讨论】:
用java.util.concurrent.atomic.AtomicInteger
代替int container[]
会更合适。【参考方案5】:
闭包被认为是副作用,它不能满足函数式编程中的纯函数限制
【讨论】:
以上是关于Java 8 支持闭包吗?的主要内容,如果未能解决你的问题,请参考以下文章