【问题标题】:Why is an ArrayList parameter modified, but not a String parameter? [duplicate]为什么修改了 ArrayList 参数,而没有修改 String 参数? [复制]
【发布时间】:2013-03-30 02:31:55
【问题描述】:
public class StackOverFlow {
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();
        al.add("A");
        al.add("B");
        markAsNull(al);
        System.out.println("ArrayList elements are "+al);

        String str = "Hello";
        markStringAsNull(str);
        System.out.println("str "+ str);
    }
    private static void markAsNull(ArrayList<String> str){
        str.add("C");
        str= null;
    }
    private static void markStringAsNull(String str){
        str = str + "Append me";
        str = null;
    }
}

这个输出:

ArrayList elements are [A, B, C]
str Hello

对于ArrayList,正在检索添加的元素。 在String 的情况下,方法调用对传递的字符串没有影响。 JVM到底在做什么?谁能详细解释一下?

【问题讨论】:

标签: java parameters call-by-value pass-by-reference


【解决方案1】:

markXAsNull 方法将本地引用设置为null。这对存储在该位置的实际值没有影响。 main 方法仍然有自己对值的引用,并且可以使用这些引用来调用 println

另外,在进行字符串连接时,toString() 会在 Object 上被调用,这就是为什么 ArrayList 以括号中的值列表形式输出。

【讨论】:

    【解决方案2】:

    Java 遵循传递值的概念(java 中没有传递引用)。因此,当您将字符串传递给函数时,它会将该字符串的 “引用副本” 发送给函数。因此,即使您在函数中将变量设置为 null,当它返回给调用者时,它也仅引用其原始值。这就是为什么原始字符串无效的原因。

    在Arraylist的情况下,引用的副本是指原始的Arraylist(在字符串的情况下也是如此)。但是当你在 ArrayList 中添加一些东西时,它指的是原始对象,因此你可以看到效果。 (尝试方法arraylist=new ArrayList(),你原来的arraylist 将保持原样)。

    如果是字符串,当你这样做时

    str=str + "abc";

    Java 创建一个新的 String 对象,该对象将引用字符串 "xyzabc"(例如 str="xyz"),并且 "xyz" 将有资格进行垃圾回收。但是由于“xyz”仍然有一个引用它的变量(原始字符串)不会被垃圾收集。但是一旦函数调用结束“xyzabc”,就会进行垃圾收集。

    讨论的总结是,只要引用指向同一个对象,您就可以在函数中进行更改,但是当您尝试更改引用 (str=str+"abc") 时,您指的是新对象在方法中,以便您的原始对象保持原样。

    【讨论】:

      【解决方案3】:

      在 Arraylist 字符串对象的情况下,添加的元素将被检索。在 String 的情况下,方法调用对传递的 String 没有影响。

      这是因为 Java 是按值传递的,而 Strings 是不可变的

      当你打电话时

      markAsNull(ArrayList<String> str)
      

      名称为str 的新引用是为al 指向的同一个ArrayList 创建的。当您 add str 上的一个元素时,它会被添加到同一个对象中。稍后您将str 放入null,但该对象添加了新值并由a1 指向。

      当你打电话时

      markStringAsNull(String str)
      {
          str = str + "Append me";
          // ...
      }
      

      str = str + "Append me"; 行通过附加给定字符串创建一个新的String 对象并将其分配给str。但同样它只是对现在指向新创建的字符串的实际字符串的引用。 (由于不可变)并且原始字符串没有改变。

      【讨论】:

      • 不是因为字符串是不可变的,而是因为str = &lt;something&gt; vs list.&lt;something&gt;
      • @immibis 请说明理由以便更好地理解
      • @SandeepSinghRana 你不明白什么?
      • ' 这是因为 str = vs list.' 究竟是什么意思
      • 它不是因为不变性。请尝试以下澄清: int t = 9;标记(t); System.out.println("int "+t);
      【解决方案4】:

      在 Java 中,您可以创建一个对象,并由多个指针引用。在任何指针上调用 mutator 方法将有效地修改唯一的对象,从而更新所有其他引用。

      但是如果你在引用上调用变量赋值语句,只有那个指针会被改变,因为它不做任何对象方面的工作(这是我能解释的最好的......)。

      将对象传递给参数将有效地复制引用,从而生成具有两个指针的单个对象 - 一个是全局的,另一个是本地的。

      还有一点,因为String 是不可变的,所以你实际上会得到一个与原来不同的新对象(事实上你必须说a = a + "a"),这就是它不会修改的原因原始字符串。

      【讨论】:

      • Java 答案中有很多指针 :)
      【解决方案5】:

      来自书籍:SCJP - Sun Certified Programmer for Java 6 Study Guide (Katty Sierra - Bert Bates) 第 3 章目标 7.3 - 将变量传递给方法

      Java 实际上是通过值传递在单个变量中运行的所有变量 虚拟机。 Pass-by-value 表示按变量值传递。这意味着,逐个复制- 变量!

      传值的底线:被调用的方法不能改变调用者的 变量,虽然对于对象引用变量,被调用的方法可以改变 object 引用的变量。改变变量有什么区别 并改变对象?对于对象引用,这意味着被调用的方法不能 重新分配调用者的原始引用变量并使其引用不同的对象, 或为空。例如,在下面的代码片段中,

      void bar() {
      Foo f = new Foo();
      doStuff(f);
      }
      void doStuff(Foo g) {
      g.setName("Boo");
      g = new Foo();
      }
      

      重新分配 g 不会重新分配 f!在 bar() 方法的最后,两个 Foo 对象 已创建,一个由局部变量 f 引用,一个由 局部(参数)变量 g。因为 doStuff() 方法有一个副本 引用变量,它有办法获取原始的 Foo 对象,例如调用 setName() 方法。但是,doStuff() 方法没有办法到达 f 参考变量。所以 doStuff() 可以改变对象 f 所指的值 to,但是 doStuff() 不能改变 f 的实际内容(位模式)。其他 换句话说,doStuff() 可以改变 f 所指对象的状态,但不能 让 f 指向不同的对象!

      【讨论】:

      • 尽管 SO 有反对的惯例,但我还是无法避免在你的回答下评论我的赞赏 :)
      猜你喜欢
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      • 2019-01-09
      • 1970-01-01
      • 1970-01-01
      • 2016-01-30
      • 2010-10-09
      相关资源
      最近更新 更多