序幕
编辑:和题主聊天后,答案还是不清楚,所以我认为有必要把答案表达得更好。
确实,Java 中的一切都是pass-by-value,也就是说,栈帧上的值是不能改变的。问题是,当将对象作为参数传递给方法时,引用(内存地址)作为值传递给函数。由于引用只是一个值,因此无法更改引用本身,但这当然并不意味着您不能更改正在被引用的内存中对象的实例。
这可能会让初学者甚至有经验的 C/C++ 程序员感到困惑。 C/C++ 中的经典指针可以更改与指针关联的引用,并且您可以更改引用指向的对象的值。因此,我声明 Java 使用我所谓的准指针,准含义(“它是,但不是”)然后是指针,它是经典的 C 指针。
考虑以下 Non Immutable 类的示例(对象不变性很重要,可能会让您感到困惑)。 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;
}
}
在上面的代码中sn-p。你会注意到输出是。
7
0
现在让我们假设当我们调用应用程序时,main 方法在地址 0x123456 的堆上创建了一个 NonImmutable 对象。当我们调用神秘1时,它真的是这样的
mystery1(0x123456) //passing the reference to Non Immutable instance as a value!
所以在函数内部我们有 ni = 0x123456,但请记住它只是引用的值,而不是经典的 C 指针。所以在神秘1中,我们先在堆上的另一个内存地址0x123abc创建另一个非不可变对象,然后我们将其设置为参数ni的值,因此我们有ni = 0x123abc。现在,让我们停下来想一想。由于在 java 中 everything 是 pass-by-value,因此设置 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
上面的代码确实会改变对象的内部状态。因此,当我们从神秘 2 中返回时,我们拥有指向将打印 7 的同一对象的指针。
现在我们了解了 Immutable 对象的工作原理,您可能会注意到传递 Objects 时有一些不寻常的情况,但上面的 准指针 似乎并不一致。嗯,它实际上是一致的,但是在面向对象语言中有一种不同的对象可以让你产生不一致的错觉,但不要被愚弄了。 Java中还有一些对象叫做不可变对象,比如这里指定的String对象(http://docs.oracle.com/javase/7/docs/api/java/lang/String.html)。
不可变对象只是意味着对象的内部状态不能改变。对,那是正确的。每次你做如下的事情
String foo = "foo";
String moo = foo + " bar";
moo 没有引用 foo,因为字符串 foo 是不可变的,因此连接操作在执行 foo + "bar" 时会在堆上创建一个全新的 String 对象。这意味着当传递 String 对象(或任何 Immutable 对象)作为方法的参数时,您不能更改 Immutable 对象的状态。因此,您对 Immutable 对象执行的任何操作实际上都会在堆上创建新对象,因此将引用新的堆对象,如上面的神秘 1 示例所示。
这是传递不可变对象的递归示例。
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);
}
}