Java方法参数拼图

时间:2009-09-24 14:03:28

标签: java arguments pass-by-reference

我偶然发现了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);
    }
}

任何人都可以解释这种奇怪吗?我注意到了与分配相同的行为。

9 个答案:

答案 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中,您可以将所有变量视为指向真实对象的指针。

这是怎么回事:

  1. 您创建一个列表。
  2. 添加17。
  3. 将列表发送给另一个函数 它拥有自己的指针 列表。
  4. 将7添加到第一个列表。
  5. 创建一个新列表并指向 指向那个的“内部”指针。
  6. 将23添加到新列表中。
  7. 打印新列表并返回。
  8. 在你原来的功能中你还是 让指针指向 和以前一样的对象。
  9. 您在第一个列表中添加了6个。
  10. 并打印第一个列表。

答案 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”将停止在范围内,并最终将被垃圾收集。 “外部”指向的对象仍然在范围内(因为外部仍处于活动状态)。