views:

482

answers:

9

Hi.

I stumbled upon a very puzzling feature(?) of Java.

It seems that using a "new" keyword to replace a method argument kind of shifts that object into a different scope:

import java.util.ArrayList;

public class Puzzle {
    public static void main(String[] args) {
        ArrayList<Integer> outer = new ArrayList<Integer>();
        outer.add(17);
        Puzzle.change(outer);
        outer.add(6);
        System.out.println(outer);

        // excpected output:
        // [23]
        // [23, 6]
        //
        // actual output:
        // [23]
        // [17, 7, 6]
    }

    public static void change(ArrayList<Integer> inner) {
        inner.add(7);
        inner = new ArrayList<Integer>();
        inner.add(23);
        System.out.println(inner);
    }
}

Can anyone explain this oddity? I noticed the same kind of behavior with assignment.

+6  A: 

This is one of the classical java question for beginners.

The inner parameter is passed by value (for a non-primitive object, it is the reference). If you act on it, it impacts the same object as in the outer code, so you see the impact in the outer method.

If you replace the object with a new object, it's not the same. When you work on the new one, you don't change the previous one, and you don't see the impact in your outer method.


Update: sorry for vocabulary mistake that caused many comments. I corrected my answer now. I believe the point was made, but it's much clearer now.

KLE
Actually, it is called by value and that's why you can't change the value of the outer reference.
Tim Büthe
Hm.. okay. So in my case JRE creates two separate references to a single object but reference reassignment does not replace the original object in memory. I guess reference copy is only good for message passing.
"The inner parameter is passed by reference." - No....!! Java does not support pass-by-reference. The 'inner' parameter is passed BY VALUE, but it is itself a reference - it's a REFERENCE that's passed BY VALUE. That's not the same as pass-by-reference.
Jesper
The difference is that if you change the value of the variable 'inner' itself (not the object that it refers to), that change will not go through to the variable that you called the method with - that would happen if it was pass-by-reference.
Jesper
Take it easy Jesper, we apparently use different words while talking about the same thing. I got it already ;)
+2  A: 

The outer reference still points to the original ArrayList that was created in your main method. Only the inner reference ever points to the new ArrayList created inside the change method, and this ArrayList is never referenced outside change.

This is expected behavior. You would need to return the reference to the inner ArrayList in order to access it from the calling scope.

Bill the Lizard
+1  A: 

You "actual output" makes perfect sense. You've re-assigned inner to a new value within the change() method and outer is no longer affected. Try tracing through the execution with a debugger and you'll understand what's going on.

Asaph
+1  A: 

You didn't shift scope, you just assigned your new ArrayList to the inner parameter. Try making inner a final parameter to prevent this.

Sam Barnum
The 'final' keyword will keep you from doing something stupid but there's no 'ref' keyword equivalent that will give the "expected" output.
Outlaw Programmer
A: 

'inner' is a reference to the same object that 'outer' references initially (as Java uses pass by reference for object parameters on methods, so inner and outer both point to the same object), then inside the method you make 'inner' reference a new object.

The important things with Java is that this doesn't alter what 'outer' is referencing in the main method. Once the method call finishes, 'inner' stops being in scope, and will be garbage collected eventually. The object that 'outer' points to is still in scope (as outer is still active).

JeeBee
+1  A: 

As far as I understood this is not a puzzle, Java only supports pass by value, I mean always copies arguments. More here.

David Santamaria
+6  A: 

In java you can think of all variables as pointers to the real objects.

What happens is this:

  1. You create an list.
  2. add 17 to it.
  3. send the list to another function that have it's own pointer to the list.
  4. add 7 to the first list.
  5. create a new list and point the "inner" pointer to that one.
  6. add 23 to the new list.
  7. print the new list and return.
  8. in the original function you still have the pointer pointing to the same object as before.
  9. you add 6 to the first list.
  10. and print the first list.
Alexander Kjäll
+1  A: 

Inlining the method your get the following equivalent code:

import java.util.ArrayList;

public class Puzzle {
    public static void main(String[] args) {
        ArrayList<Integer> outer = new ArrayList<Integer>();
        outer.add(17);

        ArrayList<Integer> inner = outer;
        inner.add(7);  // inner refers to same array list as outer

        inner = new ArrayList<Integer>();  // inner refers to new array list
        inner.add(23);
        System.out.println(inner);  // new list is printed

        outer.add(6);
        System.out.println(outer);  // outer list is printed
}

}

starblue
+8  A: 

This is a classic trip-up for Java beginners. Part of the problem is that we lack a common language to identify this. Are parameters passed by value? Objects are passed by reference? Depending on who you are talking to there will be different reactions to the words pass by value and pass by reference, even though everyone wants to say the same thing.

The way to visualize this is to understand that java variable can be one of two things. It can either contain a primitive or a reference to an object. Think of that reference as an number pointing to a location in memory. It isn't a pointer in the C++ meaning in that it doesn't point to a specific memory location, it is more of a handle, in that it lets the JVM look up a specific memory location where the object can be found. But you can visualize it this way:

   ArrayList<Integer> outer = @1234; //The first reference where the ArrayList was created.

You then call inner with the parameters of:

  Puzzle.change(@1234);

Note that you do not pass the outer variable, you pass the value of @1234. outer cannot be changed in any way by being a parameter on a method. This is what we mean when we say pass by value. The value is passed, but it is disconnected from the outer variable.

Inside Puzzle:

public static void change(ArrayList<Integer> inner) { // a new reference inner is created.
    //inner starts out as @1234
    inner.add(7);
    //now inner becomes @5678
    inner = new ArrayList<Integer>();
    //The object @5678 is changed.
    inner.add(23);
    //And printed.
    System.out.println(inner);
}

But outer is still pointing to @1234 as the method could not change that, it never had the outer variable, it just had its contents. However, since the change method started out with a reference to @1234, the object at that location could indeed be changed by the method and the results visible to any other reference to @1234.

After the change method completes, nothing references the object @5678, so it becomes eligible for garbage collection.

Yishai