Azhar
Azhar

Reputation: 1235

Groovy Collection setting empty inside method issue

I have a Groovy script with a function func(Map data) that takes a map and reinitializes passed variable with an empty map - data = [:]. The problem I face is that passing non-empty map to this function does not override a map with an empty one. Why is that?

Here is my Groovy code snippet:

Map x = [data1 : 10, data2 : 20]

def func(Map data) {
    data = [:]
}

def func2(Map data) {
    data.clear()
}

func(x)

// Setting x = [:] outside function does set x to empty

print x // prints [data1:10, data2:20]

func2(x)

print x // prints [:] (as .clear() is working)

BTW: it behaves the same for lists.

Upvotes: 1

Views: 1182

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42234

It happens, because Groovy compiler creates a new local variable data inside func(Map data) function. Take a look at decompiled code:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.Binding;
import groovy.lang.Script;
import java.util.Map;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script {
    public test() {
        CallSite[] var1 = $getCallSiteArray();
    }

    public test(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, test.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        Map x = ScriptBytecodeAdapter.createMap(new Object[]{"data1", 10, "data2", 20});
        var1[1].callCurrent(this, x);
        var1[2].callCurrent(this, x);
        var1[3].callCurrent(this, x);
        return var1[4].callCurrent(this, x);
    }

    public Object func(Map data) {
        CallSite[] var2 = $getCallSiteArray();
        var2[5].callCurrent(this, "test");
        Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);
        return var3;
    }

    public Object func2(Map data) {
        CallSite[] var2 = $getCallSiteArray();
        return var2[6].call(data);
    }
}

Check what func method is represented by at the bytecode level:

public Object func(Map data) {
    CallSite[] var2 = $getCallSiteArray();
    var2[5].callCurrent(this, "test");
    Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);
    return var3;
}

As you can see following Groovy code:

data = [:]

gets translated to something like this:

Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);

However, this kind of behavior is specific not only to Groovy, but for Java as well. Take a look at pretty similar example in Java:

import java.util.HashMap;
import java.util.Map;

final class TestJava {

    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("test", "foo");

        func(map);

        System.out.println("map outside = " + map);
    }

    static void func(Map<String, Object> map) {
        map = new HashMap<>();
        map.put("1", 2);

        System.out.println("map inside = " + map);
    }
}

If we run it we will see something similar to the Groovy use case:

map inside = {1=2}
map outside = {test=foo}

We could expect that func method should override map, but it is not happening here. If we decompile class file we will see something like this:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.util.HashMap;
import java.util.Map;

final class TestJava {
    TestJava() {
    }

    public static void main(String[] var0) {
        HashMap var1 = new HashMap();
        var1.put("test", "foo");
        func(var1);
        System.out.println("map outside = " + var1);
    }

    static void func(Map<String, Object> var0) {
        HashMap var1 = new HashMap();
        var1.put("1", 2);
        System.out.println("map inside = " + var1);
    }
}

As you can see from JRE perspective we are creating a new HashMap stored as var1 variable instead of overriding var0 variable passed to the method.

Btw, the Java version I used: OpenJDK 1.8.0_191

Upvotes: 2

Related Questions