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 支持闭包吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java 12 来了, 你还在用 Java 8 吗?

groovy 中的闭包与 java 8 中的闭包(lambda 表达式)?

理解C#中的闭包

jdk8支持tomcat9吗

Druid不支持数字?

Java1.8新特性