簡體   English   中英

遞歸方法中ArrayList的狀態

[英]State of an ArrayList in a recursive method

我有一個像這樣的遞歸方法:

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的值。

就像currNode一樣,nodeslist是一個局部變量,遞歸應該記住每個堆棧幀中nodelist的狀態。 返回上一個堆棧框架,為什么不彈出節點列表A而不是A,B(彈出myMethod(B,nodes))。

序幕

編輯:與問題所有者聊天后,答案仍然不清楚,因此我認為有必要更好地闡明答案。

的確,Java中的所有內容都是按值傳遞的 ,也就是說,堆棧框架上的值無法更改。 要注意的是,將對象作為參數傳遞給方法時,引用(內存地址)作為值傳遞給函數。 由於引用只是一個值,因此無法更改引用本身,但這當然並不意味着您不能更改正在引用的內存中對象的實例。

對於初學者甚至是經驗豐富的C / C ++程序員,這可能有些令人困惑。 在C / C ++中,經典指針可以更改與該指針關聯的引用,並且可以更改引用所指向的對象的值。 因此,我聲明Java使用了我所謂的准指針 ,即准含義(“是,但不是”),然后是指針,它是經典的C指針。

考慮以下不可變類的示例(對象不變性很重要,可能會使您感到困惑)。 非不可變基本上只是意味着對象的狀態可以改變。

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上創建另一個Non Immutable對象,然后將其設置為參數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上初始化的。 現在,執行神秘2時,會發生完全不同的事情。 當執行ni.value時,我們實際上是在執行以下內容

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

上面的代碼確實會更改對象的內部狀態。 因此,當我們從mystery2返回時,我們具有指向將打印7的同一對象的指針。

既然我們了解了不可變對象的工作原理,您可能會注意到在傳遞對象時有一些不尋常的情況,但是上述准指針似乎並不一致。 好吧,它實際上是一致的,但是面向對象語言中存在另一種對象,可以使您產生不一致的錯覺,但不要上當。 Java中還有一些稱為不可變對象的對象,例如此處指定的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 +“ bar”時,連接操作會在堆上創建一個全新的String對象。 這意味着,當傳遞String對象(或與此相關的任何不可變對象)作為方法的參數時,您不能更改不可變對象的狀態。 因此,您對Immutable對象執行的任何操作實際上都會在堆上創建新的Object,因此將像上面的mystery1示例中那樣引用新的堆對象。

這是傳遞不可變對象的遞歸示例。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM