我们首先看一下下面这样一段代码,它的输出结果是什么?
public static void main(String[] args) {
String s1=new String("张三");
change(s1);
System.out.println(s1);
}
public static void change(String s2){
s2="李四";
}
我一开始一看,String是引用类型,引用类型是引用传递。那么结果应该是“李四”,但是实际运行结果是“张三”,这个结果让我很蒙。引用类型难道不是引用传递吗?随后我又自定义了一个类Person来测试
public class Person {
String name;
public Person(String name){
this.name=name;
}
}
public static void main(String[] args) {
Person p1 = new Person("张三");
change(p1);
System.out.println(p1.name);
}
public static void change(Person p2){
p2.name="李四";
}
这次运行的结果是“李四”。这时为什么值又改变了呢?Person是引用类型,是引用传递。难道String不是引用类型,又或者它是值传递?为了弄清楚这个问题,我也在网上看了许多文章 ,把我头都搞晕了。后来结合我所看到的文章,慢慢的理清了头绪。接下来我就讲一下我的看法,有讲的不对之处还烦请指正。
首先我们不要纠结于它到底是值传递,还是引用传递。java在方法传递参数时,是将变量复制一份,然后传入方法体执行。这句话到底什么意思呢?对于基本数据类型传值,相信大家很容易理解,它是传递的一个具体值。但是对于引用类型,是将对象在堆中的地址进行复制一份进行传递,所以这样从某种程度看来它本质上还是值传递。明白了这一点,接下来我们回到上面的第一个问题(String类型作为参数传递)。
1.第一个问题在jvm中的存储如下(还未调用change(s1)时)
- 首先 String s=new String("张三"),在堆中开辟内存放对象,变量s1在栈中,存放的是堆的地址
- 将地址0x11复制一份给s2,此时s2放的是地址0x11
- 接下来再看s2="李四";要知道通过=赋值是直接先去常量池寻找是否存在与“李四”相同的值,有的话直接将其地址返回。否则创建一个值,再返回其地址。
那么调用change(s1)后,再看它在jvm中存储变化
这么来看就十分清晰了,s1和s2最终存储的地址不同。
我们再来看第二个问题(自定义对象Person传参为何最后值发生了改变)
- 首先 Person s=new Person("张三"),在堆中开辟内存放对象,变量p1在栈中,存放的是堆的地址
- 将地址0x11复制一份给s2,此时s2放的是地址0x11
- 接下来再看p2.name="李四";要知道通过=赋值是直接先去常量池寻找是否存在与“李四”相同的值,有的话直接将其地址返回。否则创建一个值,再返回其地址。与上一问题不同的是,这里我们改变的是name,而name在堆中,所以常量池中地址是复制给了name
所以最后改变后的结果如下图所示