Reputation: 1235
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
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