为什么修改了ArrayList参数,但没有修改String参数?

public class StackOverFlow { public static void main(String[] args) { ArrayList al = new ArrayList(); 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 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 ,方法调用对传递的String没有影响。 JVM到底在做什么? 谁能详细解释一下?

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

它发生的原因是Java是Pass-by-Value而String是不可变的

你打电话时

 markAsNull(ArrayList str) 

名称str的新引用是为al指向的相同ArrayList创建的。 在stradd元素时,它会添加到同一个对象中。 稍后您将strnull但该对象添加了新值,并由a1指向。

你打电话时

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

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

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

此外,在进行字符串连接时,在Object上调用toString() ,这就是为什么ArrayList作为括号中的值列表输出的原因。

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

在Arraylist的情况下,引用的副本指的是原始的Arraylist(在字符串的情况下也是相同的)。 但是当你在ArrayList中添加一些内容时,它会引用原始对象,以便你可以看到效果。 (尝试方法arraylist = new ArrayList(),你的原始arraylist将保持不变)。

在字符串的情况下,当你这样做

str = str +“abc”;

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

讨论的总结是,只要引用引用相同的对象就可以在函数中进行更改,但是当您尝试更改引用(str = str +“abc”)时,您将引用该方法中的新对象这样你的原始物体就会保持不变。

来自本书:SCJP – Sun认证的Java 6程序员学习指南(Katty Sierra – Bert Bates)第3章目标7.3 – 将变量传递给方法

Java实际上是在单个VM中运行的所有变量的值传递。 按值传递意味着按值传递值。 这意味着,变量的副本!

按值传递的底线:被调用的方法不能更改调用者的变量,但对于对象引用变量,被调用的方法可以更改引用的变量对象。 更改变量和更改对象之间有什么区别? 对于对象引用,它意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用另一个对象,或者为null。 例如,在以下代码片段中,

 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引用的对象内的值,但doStuff()不能更改f的实际内容(位模式)。 换句话说,doStuff()可以改变f引用的对象的状态,但它不能使f引用不同的对象!

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

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

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

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