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

请查看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的问题在Closure实现中。 但是,这种行为很奇怪。 这是某种bug还是我没有意识到groovy是如何工作的?

谢谢!

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

因此,一旦Groovy循环完成, f包含5并且在此之后碰巧运行的线程将打印5

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

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

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

底线 Groovy的目标是成为与Java具有最佳“阻抗匹配”的动态语言,但是现在Java有了lambdas,这两种语言仍然存在分歧。 警告程序员。

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

这是你对Closure的误解。

首先,它与匿名方法(类)或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) 

这将打印512 ,因为只要分配给f的闭包保持活动,变量myval存在。

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

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

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

为了certificate这一点,这5个Java示例在function上都做同样的事情,例如调用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); } 

我真的不知道有问题的循环的原因,但是跟随代码片段就像一个魅力:

 ["one","two","three","four"].each { tid -> Thread.start { println "Thread $tid says Hello World!" } } 

这是代码反编译的结果:

 CallSite[] arrayOfCallSite = $getCallSiteArray(); Object arr = ScriptBytecodeAdapter.createList(new Object[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(5) }); Reference f = new Reference(Integer.valueOf(0)); for (Iterator i = (Iterator)ScriptBytecodeAdapter.castToType(arrayOfCallSite[0].call(arr), Iterator.class); i.hasNext();) { ((Reference)f).set(Integer.valueOf(DefaultTypeTransformation.intUnbox(i.next()))); arrayOfCallSite[1].call(Thread.class, new _main_closure1(Test.class, Test.class, f)); } 

从这个片段可以看出,每个Thread都获得了相同的f实例