共计 2232 个字符,预计需要花费 6 分钟才能阅读完成。
前言
前段时间在群里看到相似这样一个问题,上面的代码会输入什么呢?
public void test() {
String str = "hello";
change(str);
System.out.println(str);
}
private void change(String str) {str = "world";}
过后看到这题,霎时勾起了我的回顾。遐想当年,也已经碰到过相似的问题,过后钻研了良久才搞明确,这里再记录一下这个问题的思路。
先来说一下答案:输入:hello;
解决这类问题首先要搞明确Java 到底是援用传递还是值传递。
Java 到底是援用传递还是值传递
首先来解释一下什么是援用传递,什么是值传递。
- 援用传递(pass by reference)是指在调用办法时将理论参数的地址间接传递到办法中,那么在办法中对参数所进行的批改,将影响到理论参数。
- 值传递(pass by value)是指在调用办法时将理论参数拷贝一份传递到办法中,这样在办法中如果对参数进行批改,将不会影响到理论参数。
那在 Java 中到底是援用传递还是值传递呢?其实这个问题也始终是争执一直,而且官网也没给个确切答案。然而就我集体了解,Java 是值转递。
咱们先来看一个简略的例子:
public void test() {
int a = 1;
change(a);
System.out.println("a 的值:" + a);
}
private void change(int a) {a = a + 1;}
// 输入
a 的值:1
在 test()办法中定义了一个根本类型的变量 a,而后调用 change()办法试图扭转这个变量,最初输入的还是原来的值。
首先咱们要分明,一个办法中的局部变量是存在栈中的,如果是根本类型的变量则间接存的是这个变量的值,如果是援用类型的变量则存的是值的地址,指向堆中具体的值。
下面的例子中,调用 change()办法传递的 a,其实是 a 变量的拷贝,不是真正的 a,在 change()办法中扭转的是拷贝,对真正的 a 是没有影响的。
这么一看,Java 的确是值传递,然而咱们再看上面这个例子,你就会纠结了
public void test() {User user = new User();
user.setAge(18);
change(user);
System.out.println("年龄:" + user.getAge());
}
private void change(User user) {user.setAge(19);
}
// 输入
年龄:19
看,对象里的属性被扭转了,不是值传递吗,应该不会扭转啊,这时候就有人总结了,当传的值是根本类型时是值传递、当传的是援用类型时是援用传递。真的是这样吗?
剖析这个问题,咱们须要晓得变量在 jvm 中是怎么存储的。
首先看根本类型 ,这个很简略,变量在栈中间接存的是值,传到 change() 办法的是这个变量的拷贝,因而对拷贝的变量批改不会影响原变量的值。
接着看援用类型,变量在栈中存储的是援用地址,这个地址指向堆中具体的值,如下图:
当调用 change()办法传入变量时,也是拷贝变量,然而这里的 拷贝只是栈中的援用地址,并不会拷贝堆中的数据,因而会变成下图这样:
尽管变量是拷贝,然而指向的地址是同一个,因而对变量中的数据批改时,还是会影响到原来实在的变量,然而,如果咱们批改的是变量在栈中的地址,则不会影响原变量,例如上面这段代码:
public void test() {User user = new User();
user.setAge(18);
change(user);
System.out.println("年龄:" + user.getAge());
}
private void change(User user) {user = new User();
user.setAge(19);
}
// 输入
年龄:18
这种是批改变量在栈中的地址,则不会影响原变量。
说到这里,大家差不多懂了,然而回头看最开始的那个问题,传入 String 类型的变量,String 是援用类型,按情理,原变量是会被扭转的呀,后果怎么是不变呢?
String 变量比拟非凡,咱们看 String 的源码能够晓得,String 的值是通过外部的 char[]数组来保护的,然而这个数据定义的是 final 类型的,因而,String 的值是不可变的。咱们平时批改 String 的值,其实是从新 new 了一个 String 对象,例如上面这段代码:
String a = "hello";
a = "world";
这段代码里,其实 a 变量并没有被批改成 world,只是从新 new 了一个 String 对象,这个对象的值是 world,并把这个对象的援用地址赋给了 a,原来的 hello 还是在堆中,只是这个值没有被援用,过段时间会被 gc 垃圾回收。
String 变量传值在内存中的变动如下图:
String 拷贝的是变量地址,然而它扭转不了原 String 的值,因为 String 是不可变的 ,所以在 change() 办法中是从新 new 了一个 String 对象,扭转的是新对象的值,原变量是没有影响的。
论断
Java 是值传递 。 当传的是根本类型时 ,传的是值的拷贝,对拷贝变量的批改不影响原变量; 当传的是援用类型时 ,传的是援用地址的拷贝,然而拷贝的地址和实在地址指向的都是同一个实在数据,因而能够批改原变量中的值; 当传的是 String 类型时,尽管拷贝的也是援用地址,指向的是同一个数据,然而 String 的值不能被批改,因而无奈批改原变量中的值。
<center> 扫一扫,关注我 </center>