简体   繁体   English

递归方法中ArrayList的状态

[英]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. 调试它时,我注意到当弹出一个堆栈帧并且恢复上一个堆栈帧时,节点列表即使弹出也仍然具有currNode的值。

nodeslist is a local variable just like currNode, and the recursion should remember the state of nodelist per stack frame. 就像currNode一样,nodeslist是一个局部变量,递归应该记住每个堆栈帧中nodelist的状态。 Returning to the previous stackframe, why isn't nodelist A instead of A, B (myMethod(B, nodes) was popped) . 返回上一个堆栈框架,为什么不弹出节点列表A而不是A,B(弹出myMethod(B,nodes))。

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. 的确,Java中的所有内容都是按值传递的 ,也就是说,堆栈框架上的值无法更改。 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. 对于初学者甚至是经验丰富的C / C ++程序员,这可能有些令人困惑。 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. 在C / C ++中,经典指针可以更改与该指针关联的引用,并且可以更改引用所指向的对象的值。 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. 因此,我声明Java使用了我所谓的准指针 ,即准含义(“是,但不是”),然后是指针,它是经典的C指针。

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. 现在让我们假设,当调用应用程序时,main方法在堆上的地址0x123456处创建了NonImmutable对象。 When we invoke mystery1, it really looks like this 当我们调用mystery1时,它看起来确实像这样

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. 因此在函数内部我们有ni = 0x123456,但请记住,这只是引用的一个值,而不是经典的C指针。 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. 因此,在mystery1内部,我们先在堆上的另一个内存地址0x123abc上创建另一个Non Immutable对象,然后将其设置为参数ni的值,以使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. 由于在Java中, 所有内容都是传递值 ,因此设置ni = 0x123abc并没有什么不同,如果它是原始int数据类型,则将其设置为该值。 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. 因此,传入的对象的值不会更改,因为内存地址只是一个值,与某些int,double,float,boolean等的值没有不同。

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. 我们将做与第一个示例相同的假设,即,初始Non Immutable对象是在堆上的内存地址0x123456上初始化的。 Now, when executing mystery2, something quite different happens. 现在,执行神秘2时,会发生完全不同的事情。 When executing the ni.value we are actually executing the below 当执行ni.value时,我们实际上是在执行以下内容

(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. 因此,当我们从mystery2返回时,我们具有指向将打印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 ). Java中还有一些称为不可变对象的对象,例如此处指定的String对象( 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". moo没有引用foo,因为String foo是不可变的,因此当执行foo +“ bar”时,连接操作会在堆上创建一个全新的String对象。 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. 这意味着,当传递String对象(或与此相关的任何不可变对象)作为方法的参数时,您不能更改不可变对象的状态。 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. 因此,您对Immutable对象执行的任何操作实际上都会在堆上创建新的Object,因此将像上面的mystery1示例中那样引用新的堆对象。

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. 请注意,我们没有收到像您期望的4321那样的东西。 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 . 另一个很好的参考 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. 因为当您通过java中函数的参数传递变量时,它是按值传递。 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. 这是一个教程,解释了如何通过复制值引用传递Java对象。 http://www.javaworld.com/article/2077424/learn-java/does-java-pass-by-reference-or-pass-by-value.html 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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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