我偶然发现了Java的一个非常令人费解的功能(?)。
似乎使用“new”关键字替换方法参数会将该对象转移到不同的范围:
import java.util.ArrayList;
public class Puzzle {
public static void main(String[] args) {
ArrayList<Integer> outer = new ArrayList<Integer>();
outer.add(17);
Puzzle.change(outer);
outer.add(6);
System.out.println(outer);
// excpected output:
// [23]
// [23, 6]
//
// actual output:
// [23]
// [17, 7, 6]
}
public static void change(ArrayList<Integer> inner) {
inner.add(7);
inner = new ArrayList<Integer>();
inner.add(23);
System.out.println(inner);
}
}
任何人都可以解释这种奇怪吗?我注意到了与分配相同的行为。
答案 0 :(得分:8)
这是Java初学者的经典之旅。部分问题在于我们缺乏识别这一点的共同语言。参数是按值传递的吗?对象通过引用传递?根据您与谁交谈,对于通过值传递的单词会有不同的反应并通过引用传递,即使每个人都想说同样的事情。
可视化的方法是了解java变量可以是两件事之一。它可以包含基元或对象的引用。将该引用视为指向内存中某个位置的数字。它不是C ++中的指针,意味着它不指向特定的内存位置,它更像是一个句柄,因为它允许JVM查找可以找到对象的特定内存位置。但你可以这样看待它:
ArrayList<Integer> outer = @1234; //The first reference where the ArrayList was created.
然后使用以下参数调用inner:
Puzzle.change(@1234);
请注意,您不传递外部变量,而是传递@ 1234的值。通过作为方法的参数,不能以任何方式更改外部。当我们说通过价值时,这就是我们的意思。传递该值,但它与外部变量断开连接。
Inside Puzzle:
public static void change(ArrayList<Integer> inner) { // a new reference inner is created.
//inner starts out as @1234
inner.add(7);
//now inner becomes @5678
inner = new ArrayList<Integer>();
//The object @5678 is changed.
inner.add(23);
//And printed.
System.out.println(inner);
}
但是外部仍然指向@ 1234,因为方法无法改变,它从来没有外部变量,只是它的内容。但是,由于更改方法始于引用@ 1234,因此该方法确实可以更改该位置的对象,并且结果对@ 1234的任何其他引用都可见。
更改方法完成后,没有任何内容引用对象@ 5678,因此它有资格进行垃圾回收。
答案 1 :(得分:6)
这是初学者的经典java问题之一。
内部参数按值传递(对于非基本对象,它是引用)。 如果你对它采取行动,它会影响与外部代码相同的对象,因此你可以看到外部方法的影响。
如果用新对象替换对象,则不一样。 当您使用新的时,您不会更改上一个,并且您没有看到外部方法的影响。
更新:对不起引起很多评论的词汇错误。我现在纠正了我的回答。我相信这一点已经提出,但现在更清楚了。
答案 2 :(得分:6)
在java中,您可以将所有变量视为指向真实对象的指针。
这是怎么回事:
答案 3 :(得分:2)
outer
引用仍然指向在main方法中创建的原始ArrayList。只有inner
引用指向在change
方法中创建的新ArrayList,并且此ArrayList永远不会在change
之外引用。
这是预期的行为。您需要返回对内部ArrayList的引用,以便从调用范围访问它。
答案 4 :(得分:1)
你的“实际输出”非常有意义。您已在change()方法中将内部重新分配给新值,并且外部不再受影响。尝试用调试器跟踪执行情况,你就会明白发生了什么。
答案 5 :(得分:1)
您没有移动范围,只是将新的ArrayList分配给inner
参数。尝试将inner
final
参数设为阻止此操作。
答案 6 :(得分:1)
据我所知,这不是一个谜题,Java只支持按值传递,我的意思是始终复制参数。 更多here。
答案 7 :(得分:1)
内联方法可获得以下等效代码:
import java.util.ArrayList;
public class Puzzle {
public static void main(String[] args) {
ArrayList<Integer> outer = new ArrayList<Integer>();
outer.add(17);
ArrayList<Integer> inner = outer;
inner.add(7); // inner refers to same array list as outer
inner = new ArrayList<Integer>(); // inner refers to new array list
inner.add(23);
System.out.println(inner); // new list is printed
outer.add(6);
System.out.println(outer); // outer list is printed
}
}
答案 8 :(得分:0)
'inner'是对最初引用'outer'的同一个对象的引用(因为Java使用方法对象参数的引用传递,因此内部和外部都指向同一个对象),然后在你制作的方法中'inner'引用一个新对象。
Java的重要之处在于它不会改变main方法中引用的“外部”。方法调用完成后,“inner”将停止在范围内,并最终将被垃圾收集。 “外部”指向的对象仍然在范围内(因为外部仍处于活动状态)。