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到底在做什么?有人能详细解释一下吗?
在数组列表字符串对象的情况下,添加的元素被检索。对于String,方法调用对传递的String没有影响。
这是因为Java是按值传递的,String
是不可变的
当你呼叫
markAsNull(ArrayList<String> str)
为al
指向的同一个ArrayList
创建了一个名为str
的新引用。当你在str
上add
一个元素时,它会被添加到同一个对象中。之后,您将str
添加到null
,但对象添加了新值,并由a1
指向。
当你呼叫
markStringAsNull(String str)
{
str = str + "Append me";
// ...
}
str = str + "Append me";
行通过附加给定字符串并将其赋值给str
来创建一个新的String
对象。但是它只是对实际字符串的引用现在指向新创建的字符串。(由于不可变性)和原始字符串不被改变。
markXAsNull
方法将本地引用设置为null
。这对存储在该位置的实际值没有影响。main
方法仍然有自己对这些值的引用,并且可以使用这些引用来调用println
。
同样,当做字符串连接时,toString()
在对象上被调用,这就是为什么ArrayList作为括号中值的列表输出的原因。
摘自本书:SCJP - Sun Certified Programmer for 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引用,另一个由因为doStuff()方法有一个引用变量,它有一种方法来获得原始Foo对象,例如调用setName()方法。但是,doStuff()方法没有办法到达引用变量f。所以doStuff()可以改变f引用的对象中的值但是doStuff()不能改变f的实际内容(位模式)换句话说,doStuff()可以改变f所引用对象的状态,但它不能使f指向不同的对象!
Java遵循传递值概念(Java中没有传递引用)。因此,当你将一个字符串传递给函数时,它会向函数发送一个"引用的副本"到该字符串。因此,即使在函数中将变量设置为null,当它返回给调用者时,它也只引用其原始值。这就是为什么原始字符串没有效果。
对于Arraylist, copy of reference指向原始的Arraylist(对于string也是一样)。但是当你向数组列表中添加一些东西时,它会指向原始对象这样你就能看到效果了。(try in method arraylist=new arraylist(),你原来的数组列表将保持原样)
对于字符串,当您执行
时str=str + "abc";
Java创建一个新的String对象,该对象将引用字符串"xyzabc"(例如str="xyz"),并且"xyz"将符合垃圾收集的条件。但是由于"xyz"仍然有一个变量引用它,它(原始字符串)将不会被垃圾收集。但是一旦函数调用结束,"xyzabc"就会进行垃圾收集。
讨论的总结是,只要引用引用相同的对象,您可以在函数中进行更改,但是当您尝试更改引用(str=str+"abc")时,您引用的是方法中的新对象,因此您的原始对象将保持原样。
在Java中,您可以创建一个对象,并由多个指针引用。在任何指针上调用mutator方法将有效地修改唯一对象,从而更新所有其他引用。
但是如果你在一个引用上调用变量赋值语句,只有那个指针会被改变,因为它不做任何对象端工作(这是我能解释的最好的…)。
将对象传递给形参将有效地复制引用,从而得到一个具有两个指针的单个对象——一个全局指针,另一个局部指针。
还有一点,由于String
是不可变的,您实际上会得到一个新对象,它与原始对象不同(因为您必须说a = a + "a"
),这就是它不会修改原始字符串的原因。