简体   繁体   中英

State of an ArrayList in a recursive method

I have a recursive method like so:

class MyClass {
ArrayList<ArrayList<MyNode> allSeriesOfNodes = new ArrayList<ArrayList<MyNode>();

public void myMethod(MyNode currNode, ArrayList<MyNode> nodes) {
    // make sure currNode and nodes aren't null, blah....
    nodes.add(currNode);
    for(MyNode n: otherNodeList) {
        for(listItem in n.getList()) {
            if(...) {
                myMethod(n, nodes);
        }
    }
if(currNode is a leaf and nodes is > 1) {
    allSeriesOfNodes.add(nodes);
}

Debugging it I noticed when a stack frame pops and the previous stackframe resumes, the nodes list still has the value of currNode although it popped off.

nodeslist is a local variable just like currNode, and the recursion should remember the state of nodelist per stack frame. Returning to the previous stackframe, why isn't nodelist A instead of A, B (myMethod(B, nodes) was popped) .

prologue

Edit: After chat with question owner, the answer was still not clear, so I believe it is necessary to articulate the answer better.

It is true that everything in Java is pass-by-value , that is, the values that are on the stack frame can not be changed. The catch is that when passing an object as a parameter to a method, the reference (memory address) is passed as the value to the function. Since the reference is just a value, it is not possible to change the reference itself, but that of course does not mean that you can not change the instance of the object in memory that is being referenced.

This can be some what confusing for beginning programmers and even experienced C/C++ programmers. Classic pointers in C/C++ it is possible to change the reference associated with the pointer, and you can change the value of the object the reference is pointing to. It is therefore, that I declare that Java uses what I call quasi-pointers , quasi meaning ("It is, but it is not") and then pointer, which is the classic C pointer.

Consider the following example of a Non Immutable class (object immutability is important, and could confuse you). Non Immutable basically just means that the state of the object can change.

public static void main(String[] args) {
    NonImmutable ni = new NonImmutable(0);
    mystery1(ni);
    System.out.println(ni.value);
}

public static void mystery1(NonImmutable ni) {
    ni = new NonImmutable(7);
    System.out.println(ni.value);
}

public static class NonImmutable {
    public int value;

    public NonImmutable(int value) {
        this.value =  value;
    }
}

In the above code snippet. you will notice the output is.

7
0

Now let us suppose that when we invoke the application that the main method creates a NonImmutable object on the heap at address 0x123456. When we invoke mystery1, it really looks like this

mystery1(0x123456) //passing the reference to Non Immutable instance as a value!

so inside the function we have ni = 0x123456, but remember it's just a value to the reference, not a classic C pointer. So inside mystery1, we precede to create another Non Immutable object on the heap at another memory address, 0x123abc, which we then set to the value of the parameter ni, so that we have ni = 0x123abc. Now, let's pause and think about this. Since in java everything is pass-by-value , therefore setting ni=0x123abc is no different then setting it to that value if it were a primitive int data type. Hence the value of the object is not changed that was passed in, because the memory address is just a value, which is no different then the value of some int, double, float, boolean, etc.

Now consider the next example...

public static void main(String[] args) {
    NonImmutable ni = new NonImmutable(0);
    mystery2(ni);
    System.out.println(ni.value);
}

public static void mystery2(NonImmutable ni) {
    ni.value = 7;
    System.out.println(ni.value);
}

public static class NonImmutable {
    public int value;

    public NonImmutable(int value) {
        this.value =  value;
    }
}

You will notice the output of the sample above will be.

7
7

We will make the same assumptions as the first example, that is, the initial Non Immutable object is initialize on the heap at memory address 0x123456. Now, when executing mystery2, something quite different happens. When executing the ni.value we are actually executing the below

(0x123456).value = 7 // Set the value of instance variable Non Immutable object at memory address 0x123456 to 7

The above code will indeed change the internal state of the object. Therefore, when we return from mystery2 we have the pointer to the same object that will print a 7.

Now that we understand how Immutable objects work, you may notice there are some unusual cases when Objects are passed, but the above quasi-pointer does not seem to be consistent. Well, it is actually consistent, but there is a different kind of object in Object Oriented Languages that can throw you off to give the illusion of inconsistency, but do not be fooled. There are also objects in Java called Immutable Objects such as the String object specified here ( http://docs.oracle.com/javase/7/docs/api/java/lang/String.html ).

An Immutable Object simply just means that the internal state of the object can not be changed . Yes, that is correct. Every time you do something as below

String foo = "foo";
String moo = foo + " bar";

moo is not referencing foo, because the String foo is immutable, and therefore the concatenate operation created a whole new String object on the heap when performing foo + " bar". This implies that when passing the String object (or any Immutable object for that matter), as a parameter to a method, you can not alter the state of the Immutable object. Therefore any operations you perform on the Immutable object will actually be creating new Objects on the heap, and will thus be referencing the new heap objects as in mystery1 example above.

Here is a recursive example of Immutable objects being passed.

public static void main(String[] args) {
    foo(4, "");
}

public static void foo(int i, String bar) {
    if(i==0)
        return;

    bar += Integer.toString(i);
    foo(i-1, bar);
    System.out.println(bar);
}

You will notice the output looks something like this

4321
432
43
4

Notice that we don't get something like you might expect 4321 four times. This is because in each recursive call, a new string was created on the heap to represent the new string that was created by the concatenation.

Hopefully that clears up any confusions that the questioner had, and this can be helpful for others.

Another Good Reference http://www.yoda.arachsys.com/java/passing.html .

To the problem at hand

Because when you pass variables through parameters of functions in java, it is a pass by value. In your first call to my method, you are passing in an array list object which is the same pointer through out your entire recursive algorithm. If you want nodes to be a local variable on the stack frame you will have to create a local variable, that is, not specified as a parameter in the method signature

Here is a tutorial that explains how java objects are passed by copied value reference. http://www.javaworld.com/article/2077424/learn-java/does-java-pass-by-reference-or-pass-by-value.html

yea what ever terminology you want to use. The fact is that it's the same pointer.

class MyClass {
ArrayList<ArrayList<MyNode> allSeriesOfNodes = new ArrayList<ArrayList<MyNode>();

public void myMethod(MyNode currNode) {
    List<MyNode> nodes = new ArrayList<MyNode>();  //nodes list as a local variable
    // make sure currNode and nodes aren't null, blah....
    nodes.add(currNode);
    for(MyNode n: otherNodeList) {
        for(listItem in n.getList()) {
            if(...) {
                myMethod(n);
        }
    }

    if(currNode is a leaf and nodes is > 1) {
        allSeriesOfNodes.add(nodes);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM