【问题标题】:Java method argument puzzleJava 方法参数之谜
【发布时间】:2009-09-24 14:03:28
【问题描述】:

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

谁能解释这个奇怪的地方?我注意到与分配相同的行为。

【问题讨论】:

  • 这不是一个谜题,而是一个基本概念。谷歌“按值调用与按引用调用”。

标签: java arguments pass-by-reference


【解决方案1】:

这是 Java 初学者的经典之作。部分问题在于我们缺乏一种通用语言来识别这一点。参数是按值传递的吗?对象是通过引用传递的?根据您与谁交谈,即使每个人都想说同样的话,也会对传递值和传递值这两个词有不同的反应。

可视化的方法是理解 java 变量可以是两件事之一。它可以包含原语或对对象的引用。将该引用视为指向内存中某个位置的数字。它不是 C++ 中的指针,因为它不指向特定的内存位置,它更像是一个句柄,因为它让 JVM 查找可以找到对象的特定内存位置。但是您可以通过这种方式对其进行可视化:

   ArrayList<Integer> outer = @1234; //The first reference where the ArrayList was created.

然后您使用以下参数调用内部:

  Puzzle.change(@1234);

请注意,您不传递外部变量,而是传递@1234 的值。 external 不能通过作为方法的参数以任何方式更改。这就是我们所说的按值传递的意思。该值被传递,但它与外部变量断开连接。

谜题内部:

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

但是outer仍然指向@1234,因为方法无法改变它,它从来没有outer变量,它只有它的内容。但是,由于 change 方法以对 @1234 的引用开始,因此该方法确实可以更改该位置的对象,并且结果对任何其他对 @1234 的引用都是可见的。

更改方法完成后,没有任何东西引用对象@5678,因此它可以进行垃圾回收。

【讨论】:

    【解决方案2】:

    在 java 中,您可以将所有变量视为指向真实对象的指针。

    会发生什么:

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

    【讨论】:

      【解决方案3】:

      这是一个经典的java初学者问题。

      内部参数通过值传递(对于非原始对象,它是引用)。 如果您对其进行操作,它会影响与外部代码中相同的对象,因此您会看到外部方法中的影响。

      如果将对象替换为新对象,则不一样。 当您使用新方法时,您不会更改以前的方法,也不会看到外部方法的影响。


      更新:对于导致许多 cmets 的词汇错误,我们深表歉意。我现在更正了我的答案。我相信这一点已经提出,但现在更清楚了。

      【讨论】:

      • 其实是按值调用的,所以不能改变外部引用的值。
      • 嗯……好吧。因此,在我的情况下,JRE 创建了对单个对象的两个单独的引用,但引用重新分配不会替换内存中的原始对象。我猜参考副本只对消息传递有用。
      • "内部参数通过引用传递。" - 不....!! Java 不支持按引用传递。 'inner' 参数是按值传递的,但它本身就是一个引用——它是一个按值传递的 REFERENCE。这与传递引用不同。
      • 不同之处在于,如果您更改变量 'inner' 本身的值(而不是它所引用的对象),该更改将不会通过您调用该方法的变量 -如果它是按引用传递,就会发生这种情况。
      • 放轻松 Jesper,我们显然在谈论同一件事时使用了不同的词。我已经知道了;)
      【解决方案4】:

      outer 引用仍然指向在您的 main 方法中创建的原始 ArrayList。只有 inner 引用指向在 change 方法内创建的新 ArrayList,并且此 ArrayList 永远不会在 change 之外引用。

      这是预期的行为。您需要返回对内部 ArrayList 的引用才能从调用范围访问它。

      【讨论】:

        【解决方案5】:

        您的“实际输出”非常有意义。您已在 change() 方法中将 inner 重新分配给一个新值,而 outer 不再受到影响。尝试使用调试器跟踪执行,您就会明白发生了什么。

        【讨论】:

          【解决方案6】:

          您没有改变范围,您只是将新的 ArrayList 分配给 inner 参数。尝试将 inner 设为 final 参数以防止这种情况发生。

          【讨论】:

          • “final”关键字可以防止您做一些愚蠢的事情,但没有等效的“ref”关键字可以提供“预期”的输出。
          【解决方案7】:

          据我了解,这不是一个谜,Java 只支持按值传递,我的意思是总是复制参数。 更多here

          【讨论】:

            【解决方案8】:

            内联你得到以下等效代码的方法:

            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
            }
            

            }

            【讨论】:

              【解决方案9】:

              'inner' 是对与'outer' 最初引用的同一对象的引用(因为Java 对方法上的对象参数使用按引用传递,因此inner 和outer 都指向同一个对象),然后在您创建的方法内部'inner' 引用一个新对象。

              Java 的重要之处在于它不会改变 main 方法中“外部”引用的内容。一旦方法调用完成,'inner' 将停止在作用域内,最终将被垃圾回收。 'outer' 指向的对象仍在范围内(因为外部仍然处于活动状态)。

              【讨论】:

                猜你喜欢
                • 2011-08-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-11-29
                • 2011-09-20
                • 1970-01-01
                • 2015-02-10
                相关资源
                最近更新 更多