For 循环在 Groovy 和 Java 中的工作方式不同

2022-09-03 02:39:19

请看一下 groovy 中的以下代码片段:

def static void main(String... args) {
    def arr = [1, 2, 3, 4, 5]
    for (int f in arr) {
        Thread.start { print f + ', '}
    }
}
Out: 2, 3, 5, 5, 5,

我对这个输出感到惊讶。为什么“5”被打印了好几次?此外,在Java中运行等效代码看起来一切正常:

public static void main(String[] args) {
    int[] arr = new int[]{1, 2, 3, 4, 5};
    for (int f : arr) {
        new Thread(() -> { System.out.print(f + ", "); }).start();
    }
}
Out: 1, 5, 4, 3, 2,

谁能解释一下为什么会这样?看起来groovy的问题在闭包实现中。但是,这种行为非常奇怪。是某种错误,还是我只是没有意识到时髦是如何工作的?

谢谢!


答案 1

Java 闭包在 创建时关闭在不可变值上,而 Groovy 闭包在可变变量上关闭。ff

因此,一旦Groovy循环完成,包含和线程恰好在那之后运行将打印。f55

Java 闭包可以在最终或“有效最终”的变量引用上关闭,这意味着它除了名称之外都是最终的。请参阅 Java 8:Lambdas,第 1 部分。这就是内部类可以做的事情,加上一些有用的便利。

Groovy 闭包是非常不同的对象,它们早于 Java 闭包。请参阅 Groovy 闭包,其中示例修改封闭作用域中的变量。{ ++item }

Groovy 将闭包定义为闭包类的实例。这使得它与Java 8中的lambda表达式非常不同。委派是 Groovy 闭包中的一个关键概念,在 lambda 中没有等价物。更改委托或更改闭包的委派策略的能力使得在Groovy中设计漂亮的域特定语言(DSL)成为可能。

底线Groovy的目标是成为与Java具有最佳“阻抗匹配”的动态语言,但现在Java有了lambda,这两种语言继续分化。注意程序员。


答案 2

这不是Groovy中的“闭包实现”的问题。

这是你对什么是闭包的误解。

首先,它与匿名方法(类)或Lambda(Java 8 +)不同
它与 JavaScript 闭包相同。

闭包对作用域内的局部变量具有完全读/写访问权限,这意味着在封闭方法中定义但在闭包之外定义的变量。这些变量存在,并且可以由任何可以访问它们的代码更新,并且在定义它们的方法退出(返回)后,它们继续存在。

你真的应该阅读更多关于闭包的信息,无论是在Groovy还是JavaScript文档和示例中。JavaScript充斥着闭包的使用,所以你会发现很多关于这个主题的文档。

下面是一个简短的介绍:

def a() {
    def myval = 0
    return { x -> myval += x } // <-- Returns a closure
}
def f = a()
print f(5)
print f(7)

这将打印 和 ,因为只要赋值为活动的闭包,变量就存在。512myvalf

或者这里是JavaScript版本:https://jsfiddle.net/Lguk9qgw/

相比之下,Java不能做到这一点,因为Java没有闭包,即使新的Lambdas也没有。Java的匿名类及其Lambda等效物要求所有外部变量都是不变的,即,无论是以这种方式显式定义,还是由编译器推断(Java 8中的新功能)。final

这是因为Java实际上复制了该值,并且要求该值是确保你不会注意到,除非你反汇编生成的字节码。final

为了说明这一点,这5个Java示例在功能上都做同样的事情,例如调用将返回:test1().applyAsInt(5)12

// Using Lambda Expression
public static IntUnaryOperator test1() {
    final int f = 7;
    return x -> x + f;
}

// Using Lambda Block
public static IntUnaryOperator test2() {
    final int f = 7;
    return x -> { return x + f; };
}

// Using Anonymous Class
public static IntUnaryOperator test3() {
    final int f = 7;
    return new IntUnaryOperator() {
        @Override public int applyAsInt(int operand) { return operand + f; }
    };
}

// Using Local Class
public static IntUnaryOperator test4() {
    final int f = 7;
    class Test4 implements IntUnaryOperator {
        @Override public int applyAsInt(int operand) { return operand + f; }
    }
    return new Test4();
}

// Using Nested Class
private static final class Test5 implements IntUnaryOperator {
    private final int f;
    Test5(int f) { this.f = f; }
    @Override public int applyAsInt(int operand) { return operand + this.f; }
}
public static IntUnaryOperator test5() {
    final int f = 7;
    return new Test5(f);
}