先来看一段代码
public class MyClassA {
public static void main(String[] args){
String x = new String("hello");
change(x);
System.out.println(x);
}
private static void change(String x) {
x = "world";
}
}
输出结果是hello,那么change()方法中到底做了什么?
引用x指向在堆中的String对象”hello”,x作为参数传递到change()方法中,它仍然指向堆中”hello”。
下面重点来了
往往有一种错误的说话是这么讲的:
Java是值传递的,而x的值就是堆中对象”hello”的引用。当方法change()被调用时,由于String具有不可变性,所以在堆中会创建一个新对象”world”,而x是指向堆中对象”world”。
注意这种说法是错误的!!!
正确的解释应该是这样的:
当字符串”hello”被创建时,首先会在堆中开辟一块内存空间存储字符串对象所需要的内存空间。然后对象分配给在栈中的变量x,也就是说变量x引用堆中”hello”字符串对象。
变量x其实本身不是引用,栈中为了存储变量x开辟一块空间,其中变量x在其存储单元中存储了字符串对象”hello”在堆中的内存地址,这样就达到了变量x引用指向字符串对象的作用。
Java中只有值传递,当变量x作为参数传递给change(String x)方法时,参数x的值就是变量x也就是“hello”对象引用的一个副本,而不是“hello”对象复制一个引用副本给参数x。两种方式的区别在于变量x在传入change(String x)时,将存储单元存储的字符串对象的内存地址传递给了方法中参数x,参数x在栈中是开辟的另一块空间,这样参数x和变量x所存储的都是字符串对象的内存地址,这样两者就都指向了同一个字符串对象。
这样的解释是合理,但是String是具有不可变性的
x = "world";
由于String不可变,所以又创建了新的对象”world”。这时因为外部的变量x和方法内部的变量x两者是在栈中两块不同的空间,所以方法内部的x也就是参数x所引用的对象改变了,并不影响外部的变量x所指向的对象。
其实上面的说法有一点点不严谨,在change(String x)中,x = “world”;这个字符串对象应该是在常量池中的,因为字符串对象并不是通过new创建的,总之只要明白是新建了一个新的字符串对象就是了。
那么如果栈中的引用变量所指向的并不是一个String类型的对象,结果就不一样了。
... ...
class person {
public static String name = "Jack";
... ...
}
... ...
//定义一个改变对象属性的方法
public static void changeName(Person p) {
p.name = "Rose";
}
... ...
public static void main(String[] args) {
//定义一个Person对象,person是这个对象的引用
Person person = new Person();
//先显示这个对象的name属性
System.out.println(person.name);
//调用changeName(Person p)方法
changeName(person);
//再显示这个对象的name属性,看是否发生了变化
System.out.println(person.name);
}
第一次显示:“Jack”
第二次显示:“Rose”
和上面的道理一样,只不过这回堆中存放的对象是可变的。首先在主函数中引用变量person存放在栈中,person的存储单元中存放的是Person对象在堆中的内存地址。调用changeName(person)方法,将引用变量传递给方法参数p,参数p是栈中的另一块空间,person变量把其存储单元中存放的Person对象内存地址传递给了参数p,所以p也指向了同一个对象,然后在changeName方法中堆Person对象进行操作,所以经过changeName方法后Persond对象中的属性值改变了。
所以说Java中只存在值传递,无论是基本数据类型还是String类型或者其他的类都是值传递