3

I came across this today and I can't quite get my head around what is happening.

The intention is to create an anonymous method that can change the argument objects. I though I came up with a smart way of passing values and modifying them between different objects without having explicit knowledge of the other object, but something is awry. The following code below outlines the general problem:

void foo() {
    String a = "foo";

    MethodMap.addMethod("1", new Method() {
        @Override
        public void execute(Object ... args) {
             args[0] = "bar";
        }
    } );
    MethodMap.invoke("1", a);
    System.out.println(a);
}

class MethodMap {
    private static Map<String, Invocation> map = new HashMap<>();

    public static boolean addMethod(String key, Invocation method) {
        map.put(key, method);
    }

    public static void invoke(String key, Object ... args){
        map.get(key).execute(args);
    }
}

public interface Invocation {
    public void execute(Object ... args);
}

My intention was that this code should output bar, but it outputs foo. I'm not quite sure why though. Isn't Java-objects passed by reference? In that case, shouldn't I be able to modify them?

Could someone please explain what it is I am missing?

My knowledge of terminology in the field might actually be what's limiting my ability to search for this online, cause I have no idea what words to Google.

Thanks // Simon

eddie
  • 1,252
  • 3
  • 15
  • 20
Gikkman
  • 752
  • 6
  • 21
  • As an aside, your code looks very much like a [common flavor of aspect-oriented programming](http://aopalliance.sourceforge.net/doc/org/aopalliance/intercept/MethodInterceptor.html). You might want to take a look at some AOP solutions for inspiration (or perhaps to use instead). – Mark Peters Aug 10 '15 at 14:34

2 Answers2

5

Java is always pass-by-value. You simply cannot change what a local variable points to other than reassigning it in the method in which it is declared. Unlike some other languages, you cannot pass a "by reference" and have another method update what it points to.

By specifying arg[0] = "bar", you are successfully assigning the value "bar" to the first element of your parameter array. But this has no effect on a.

See:

Community
  • 1
  • 1
Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • Wow... I had totally gotten it all backwards. Gosh I feel stupid. So I create a wrapper around it, and then modify it through the wrapper? – Gikkman Aug 10 '15 at 14:27
  • @Gikkman: It's not at all a stupid mistake; some other languages support this just fine! Yes you can create a wrapper around it and that will work (if you want one out of the box, the `AtomicReference` concurrency helper might be suitable). – Mark Peters Aug 10 '15 at 14:28
  • Oh, that explains a lot... I was so certain I had seen this kind of pattern in a text book before. Well, thanks for the quick answers :-) – Gikkman Aug 10 '15 at 14:30
1

args[] is a local variable in your anonymous MethodMap instance, and it refers to a temporary array that was implicitly created to hold the extra arguments to the .invoke(...) call.

All you are doing when you assign args[0] is updating the temporary array.


void foo() {
    // a is a local variable in your foo() method that refers to an
    // immutable String object with the value, "foo".
    String a = "foo";

    MethodMap.addMethod("1", new Method() {
        @Override

        // args is a local variable in the execute() method, that refers
        // to a temporary Array object. 
        public void execute(Object ... args) {

             // This assignment changes the zeroth element of the temporary
             // array to point to a String object with the value, "bar".
             args[0] = "bar";
        }
    } );

    // The compiler turns your MethodMap.invoke() call into this:
    //       Array<Object> tmp = new Array<Object>[1];
    //       tmp[0] = a;   // tmp[0] now refers to the same immutable String as a.
    //       MethodMap.invoke("1", tmp);
    // The tmp array is ignored after the invoke call returns.
    MethodMap.invoke("1", a);

    System.out.println(a);
}
Solomon Slow
  • 25,130
  • 5
  • 37
  • 57