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)
这将打印5
和12
,因为只要分配给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实例