递归方法中ArrayList的状态

时间:2014-08-03 22:39:14

标签: java recursion

我有一个像这样的递归方法:

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);
}

调试它我注意到当堆栈帧弹出并且前一个堆栈帧恢复时,节点列表仍然具有currNode的值,尽管它弹出了。

nodeslist是一个局部变量,就像currNode一样,递归应该记住每个堆栈帧的节点列表的状态。回到上一个堆栈帧,为什么不是节点列表A而不是A,B(myMethod(B,节点)被弹出)。

1 个答案:

答案 0 :(得分:2)

<强>序言

编辑:与问题所有者聊天后,答案仍然不明确,所以我认为有必要更好地阐明答案。

Java中的所有内容都是按值传递,也就是说,无法更改堆栈帧上的值。问题在于,当将对象作为参数传递给方法时,引用(内存地址)作为值传递给函数。由于引用只是一个值,因此无法更改引用本身,但这当然并不意味着您无法更改正在引用的内存中的对象实例。

对于初学程序员甚至是经验丰富的C / C ++程序员来说,这可能会让人感到困惑。 C / C ++中的经典指针可以更改与指针关联的引用,并且可以更改引用指向的对象的值。因此,我声明Java使用我称之为准指针,准意义(&#34;它是,但它不是&#34;)然后指针,这是经典C指针。

考虑以下非永久性类的示例(对象不变性很重要,可能会让您感到困惑)。 Non Immutable基本上只是意味着对象的状态可以改变。

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;
    }
}

在上面的代码段中。你会注意到输出是。

7
0

现在让我们假设当我们调用应用程序时,main方法在堆上的地址0x123456处创建一个NonImmutable对象。当我们调用mystery1时,它看起来真的像这样

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

所以在函数内部我们有ni = 0x123456,但请记住它只是引用的值,而不是经典的C指针。所以在mystery1中,我们先在另一个内存地址0x123abc上创建另一个非永久对象,然后我们将其设置为参数ni的值,这样我们就得ni = 0x123abc。现在,让我们停下来思考一下。因为在java 一切按值传递,所以设置ni = 0x123abc没有区别,那么如果它是原始int数据类型则将其设置为该值。因此,传入的对象的值不会改变,因为内存地址只是一个值,它与某些int,double,float,boolean等的值没有区别。

现在考虑下一个例子......

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;
    }
}

您会注意到上面示例的输出将是。

7
7

我们将做出与第一个示例相同的假设,也就是说,初始的Non Immutable对象在堆上的内存地址0x123456处初始化。现在,在执行myst​​ery2时,会发生一些完全不同的事情。执行ni.value时,我们实际执行下面的

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

上面的代码确实会改变对象的内部状态。因此,当我们从mystery2返回时,我们有指向同一个对象的指针,它将打印一个7。

现在我们已经了解了Immutable对象的工作原理,你可能会注意到在传递对象时有一些不寻常的情况,但上面的准指针似乎并不一致。嗯,它实际上是一致的,但是面向对象语言中有一种不同类型的对象可能会让你产生不一致的错觉,但不要被愚弄。 Java中还有一些名为Immutable Objects的对象,例如此处指定的String对象(http://docs.oracle.com/javase/7/docs/api/java/lang/String.html)。

不可变对象只是意味着对象的内部状态可以不能更改。对,那是正确的。每次你做以下的事情

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

moo没有引用foo,因为String foo是不可变的,因此在执行foo +&#34时,连接操作在堆上创建了一个全新的String对象。 BAR&#34 ;.这意味着当传递String对象(或任何不可变对象)时,作为方法的参数,您不能改变Immutable对象的状态。因此,您对Immutable对象执行的任何操作实际上都是在堆上创建新对象,因此将引用新堆对象,如上面的mystery1示例所示。

这是传递Immutable对象的递归示例。

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);
}

您会注意到输出看起来像这样

4321
432
43
4

请注意,我们没有得到像你预期的那样4321次。这是因为在每次递归调用中,在堆上创建了一个新字符串,以表示由串联创建的新字符串。

希望能够解决提问者所遇到的任何混淆,这对其他人有帮助。

另一个好参考 http://www.yoda.arachsys.com/java/passing.html

解决手头的问题

因为当你通过java中的函数参数传递变量时,它是一个值传递。在您第一次调用我的方法时,您传入的是一个数组列表对象,该对象是整个递归算法的相同指针。如果希望节点成为堆栈帧上的局部变量,则必须创建局部变量,即未在方法签名中指定为参数

这是一个教程,解释了如何通过复制的值引用传递java对象。 http://www.javaworld.com/article/2077424/learn-java/does-java-pass-by-reference-or-pass-by-value.html

是你想要使用的术语。事实是,它是同一个指针。

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);
    }
}