1、必知概念:
- 实参:方法被调用时传入的实际值。
- 形参:在定义方法时括号内定义的参数列表即为形参,它用来接收方法调用时传入的实参。
- 值传递:当方法被调用时,实参通过形参将其副本传入方法内,接下来对形参的操作就是在对实参副本的操作,并不会影响实参本身。
- 引用传递:当方法被调用时,实参的地址值通过形参将其传入方法内,所以对形参的改变就会映射到实参上。
注意:值传递与引用传递的区别并不在于被传递参数的类型,也就是说,并不是传入的参数是基本数据类型就是值传递,同理,并不是传入的参数是引用类型就是引用传递。
2、Java中只有值传递的原因:
public class Test {
public static void main(String[] args) {
int num = 9;
System.out.println("修改前的实参为:" + num);
change(num);
System.out.println("修改后的实参为:" + num);
}
private static void change(int temp) {
System.out.println("传入的形参为:" + temp);
temp++;
System.out.println("修改后的形参为:" + temp);
}
}
可以看出,这个过程虽然改变了形参,但是并没有改变实参的值,所以说是一次值传递过程。接下来就按照Java内存区域进行进一步分析:
当程序启动时,首先执行main方法,此时就会在本地方法栈中创建一个关于main方法的栈帧进行压栈,栈帧中包含局部变量表(目前仅有num)及其他信息,因为在main方法中调用了change方法,所以要创建一个关于change方法的栈帧再次压栈,如下图所示,
小结:修改的值是main中传入的num副本值,所以并不会修改num本身。
public class TestUser {
public static void main(String[] args) {
Person person = new Person(10, "一诺");
System.out.println("修改前的实参为:" + person);
change(person);
System.out.println("修改后的实参为:" + person);
}
private static void change(Person p) {
p.setAge(100);
p.setName("Cat");
}
}
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Amazing,为什么Person的属性发生了改变,不是值传递吗?不是不改变实参值吗?那么我们就通过Java内存区域来进一步分析:
可以看出,传递的参数并不是Person对象的具体值,而是它的地址值,所以实参、形参指向的是堆内存中的同一个Person对象,这样我们对形参的修改就会体现在实参上。
这样理解是没问题,但是我又想了一下,这样通过对象的地址值改变对象属性,这不是引用传递吗?不是说Java中只有值传递吗?带着这个疑问,我又反复看了好多次值传递、引用传递的概念,值传递每次传递的都是实参的副本啊,那要是顺着这条线想下去,那么change中p的值就是person地址值(0x11)的副本值呗,这样就可以解释了上述这个过程是值传递的过程了(不知道这样理解对不对,不对的话还请大家指正~)。
- 当形参为String等不可变类(Integer等包装类)时:
public class TestString {
public static void main(String[] args) {
String str = new String("孙行者");
System.out.println("字符串修改前:" + str);
change(str);
System.out.println("字符串修改后:" + str);
}
private static void change(String str) {
str = "行者孙";
}
}
???为什么又和第二种情况不一样???String不是也是引用类型吗?怎么会出现这种情况呢?带着充满问号的脑袋,接着来按照Java内存区域进一步分析吧:
可以看出,确实是将实参的副本传入给方法内,但是在change方法中做了一个 str = "行者孙" 的操作,因为String是不可变类,它一旦初始化就不可以再更改值了,因此这个操作是重新实例化了一个String对象,str也由 0x11 变为了 0x10,而0x11所指向的值没有改变,所以自然main方法中的str值不会变啦~
参考:
总结:
站在巨人的肩膀上才能看的更远,很庆幸能在网上找到前辈们写的文章来解答我的疑惑,同时也希望自己总结的文章能够帮助到别人。而对于这个本文分享的问题,之前我也很不理解为什么Java中只有值传递,但是在了解完Java内存区域后再看这些就很好理解了,在这篇文章我也没有很详细的介绍这部分内容,所以还是推荐对此问题有疑惑的朋友先去了解一下Java内存区域。