Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For loop works different in Groovy and Java

Please take a look at the following code snippet in 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,

I was surprised by this output. Why "5" was printed several times? Moreover, everything looks good running equivalent code in 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,

Can anyone explain why this is the case? Looks like the issue with groovy is in Closure implementation. However, this behaviour is pretty weird. Is it some kind of bug or I just do not realize how groovy works?

Thanks!

like image 855
Dmitry Avatar asked Dec 15 '15 07:12

Dmitry


People also ask

Can we use for loop in Groovy?

Groovy Programming Fundamentals for Java Developers The following diagram shows the diagrammatic explanation of this loop. The for-in statement can also be used to loop through ranges. The following example shows how this can be accomplished. The for-in statement can also be used to loop through Map's.

How do you write a loop in Groovy?

Groovy Programming Fundamentals for Java Developers S.No. The while statement is executed by first evaluating the condition expression (a Boolean value), and if the result is true, then the statements in the while loop are executed. The for statement is used to iterate through a set of values.

How do you break a loop in Groovy?

Groovy - Break StatementThe break statement can also be used with while and for statements. Executing a break statement with any of these looping constructs causes immediate termination of the innermost enclosing loop.

How do I iterate a map in Groovy?

We can iterate through entries using the each() and eachWithIndex() methods. The each() method provides implicit parameters, like entry, key, and value, which correspond to the current Entry. The eachWithIndex() method also provides an index in addition to Entry. Both the methods accept a Closure as an argument.


2 Answers

A Java closure closes over the immutable value in f when created, while the Groovy closure closes over the mutable variable f.

So once the Groovy loop finishes, f contains 5 and threads that happen to run after that will print 5.

Java closures can close over a variable reference that's final or “effectively final” meaning that it’s final in all but name. See Java 8: Lambdas, Part 1. That's what inner classes can do plus some useful convenience.

Groovy closures are very different objects, and they predate Java closures. See Groovy Closures, where the example { ++item } modifies a variable from the enclosing scope.

Groovy defines closures as instances of the Closure class. It makes it very different from lambda expressions in Java 8. Delegation is a key concept in Groovy closures which has no equivalent in lambdas. The ability to change the delegate or change the delegation strategy of closures make it possible to design beautiful domain specific languages (DSLs) in Groovy.

Bottom line Groovy aims to be the dynamic language with the best "impedance match" to Java, but now that Java has lambdas, the two languages continue to diverge. Caveat programmer.

like image 134
Jerry101 Avatar answered Oct 07 '22 18:10

Jerry101


It is not an issue with the "Closure implementation" in Groovy.

It is with your misunderstanding of what a Closure is.

First of all, it is not the same as an anonymous method (class) or Lambda (Java 8+).
It is the same as a JavaScript closure.

A closure has full read/write access to local variables that are in scope, meaning variables defined in the enclosing method, but outside the closure. Those variables exist and can be updated by any code with access to them, and they continue to exist after the method defining them exits (returns).

You really should read much more about closures, either in Groovy or JavaScript documentation and examples. JavaScript is rife with use of closures, so you'll find lots of documentation on the subject.

Here's a short intro:

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

This will print 5 and 12, because the variable myval exists as long as the closure assigned to f stays alive.

Or here is the JavaScript version: https://jsfiddle.net/Lguk9qgw/

In contrast, Java cannot do that, because Java doesn't have closures, not even with the new Lambdas. Java's anonymous classes, and their Lambda equivalent, require all outside variables to be invariant, i.e. final, whether explicitly defined that way, or inferred by the compiler (new in Java 8).

This is because Java in reality copies the value, and requiring the value to be final ensures that you don't notice, unless you disassemble the generated bytecode.

To show this, these 5 Java examples all do the same thing, functionally, e.g. calling test1().applyAsInt(5) will return 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);
}
like image 24
Andreas Avatar answered Oct 07 '22 18:10

Andreas