|
大家好,我是 杰哥
还记不记得,刚开始找工作的时候,笔试题中,往往会有那么几道代码题是这样的:给你几行代码,让你判断输出的结果是什么。经常出现的是判断程序启动时,父类子类的构造方法、静态方法、静态属性与普通方法等的执行顺序。还有一种类型则是,一个值,调用了一个 void 方法之后,判断打印出来的是修改以后的值是什么,一般都是考你经过这个方法的调用,这个值会不会发生变化
这些套路题,往往在最初会难倒很多基础较为薄弱的童鞋,但是若搞懂了这些背后的原理,那你还怕什么,这些对你来说,岂不是送分题?推你进大厂呢?
那么,今天我们就来一起看看,java 中的值传递与引用传递,消灭以前的一些错误理解,彻底搞清楚 java 在调用中,究竟是如何进行参数的传递的?
一 理论先行
要搞清楚 java 中的方法调用,到底是值传递,还是引用传递之前,先来看看 值传递和引用传递分别是什么
值传递(pass by value)是指在调用函数时将实际参数 复制 一份传递到函数中,而并不是将这个值直接传递给函数
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,而传递过来的地址还是与之前的地址指向同一个值,那么要是修改了这个参数,就会影响这个值的改变
二 实践寻找
很多人都说 Java 中实际上是只有值传递,其实,我也很认同,并通过以下几个例子,做了验证
看的时候,可以先别看答案,先自己猜一猜会是什么结果哦
1 传递类型为基本类型 int
public static void main(String[] args) {
int value = 10;
setValue(value);
System.out.println("调用后:"+ value);
}
public static void setValue(int value) {
value = 2 * value;
System.out.println("调用时:" + value);
}
打印结果:
调用时:20
调用后:10
说明
value 的值并没有因为调用了 setValue()方法,而发生改变
也就是说,我们验证了 java 中的 基本类型 是采用值传递的方式的
2 传递类型为引用类型: String
public static void main(String[] args) {
String value = "hello";
setValue(value);
System.out.println("调用后:"+ value);
}
public static void setValue(String value) {
value = value + "123";
System.out.println("调用时:" + value);
}
打印结果:
调用时:hello123
调用后:hello
说明
value 的值并没有因为调用了 setValue()方法,而发生改变
那么,我们也验证了 java 中的 引用类型 是采用值传递的方式的
中场休息
来来来,那么也就是说,我们基本上可以下个结论,就是说:java 的确如大家所说,在方法调用过程中,是采用值传递的
我们来分析一下他们调用过程中的步骤
我们知道,java 在进行方法调用时,会将线程中每个方法分别放入栈中,先进入的方法,最终被放在了最低层,因此会在最后被执行,最后入栈的方法,则会最先被执行
那么,对于 main 方法和 setValue() 方法,在 栈中存放的 方式如下:

main 方法 中的 value,被传递给 setValue() 方法,最终它的值并没有改变,也就是说,它是值传递:即传递 给 setValue() 的参数,是 value 的副本,而并不是 value 本身,所以,我们在 setValue() 方法中,即时为 value 重新赋值,由于它是另一个 value,那么,原来的 value 值当然不会被修改了
也许,你会对此提出质疑,因为你在想,即使 基本类型 和这里的 String 类型,虽然被印证了是采用值传递的,但是要是其他引用类型呢?比如说 一个实体类对象的值,往往就因为调用方法,而发生变化
是这样吗?我们来接着看看
3 传递类型为引用类型:User
public static void main(String[] args) {
User user = User.builder().age(11).name("杰哥").build();
setValue(user);
System.out.println("调用后:"+ user);
}
public static void setValue(User user) {
user = user.builder().age(15).name("李四").build();
System.out.println("调用时:" + user);
}
打印结果:
调用时:User(id=0, name=李四, age=15)
调用后:User(id=0, name=杰哥, age=11)
说明
value 的值并没有因为调用了 setValue()方法,而发生改变。杰哥,还是那个杰哥
这下你相信了吧?引用类型,也是采用了值传递的:在方法调用过程中,只会传递一份参数的副本,因此并不会影响原来的值
如果没有完全说服你,那么我们再来一个例子,传递类型为集合
4 传递类型为 集合:List
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add(&#34;hello&#34;);
list.add(&#34;world&#34;);
setValue(list);
System.out.println(&#34;调用后:&#34;+ list);
}
public static void setValue(List<String> list) {
list = new ArrayList<>();
list.add(&#34;world&#34;);
list.add(&#34;hello&#34;);
System.out.println(&#34;调用时:&#34; + list);
}
打印结果:
调用时:[world, hello]
调用后:[hello, world]
说明:
并且将 list 换成 List<User> 类型的结果也一模一样,调用方法之后的 list 值并不会发生变化。这下,我猜你应该已经确信了,java 的确是值传递的,嗯,没毛病
但是,估计有一少部分人,还是会举出一个反例,来推倒这个理论,为了加深大家的理解,我们也一起来看看 这个所谓的&#34;反例&#34;
三 反例说明
public static void main(String[] args) {
User user = User.builder().age(11).name(&#34;杰哥&#34;).build();
setValue(user);
System.out.println(&#34;调用后:&#34;+ user);
}
public static void setValue(User user) {
user.setAge(12);
user.setName(&#34;李四&#34;);
System.out.println(&#34;调用时:&#34; + user);
}
先来猜猜打印结果。。。。。。
是的,打印结果如下:
调用时:User(id=0, name=李四, age=12)
调用后:User(id=0, name=李四, age=12)
说明:
首先,user 的值,在调用了 setValue() 方法之后,是被修改了的,怎么样,是不是会有一点点蒙?
哈哈,没有关系,往下看
1 对比
这个可以跟第二章中的第 3 个例子,对比着看,同样是 User 对象的传递,最终前者未发生变化,而后者却发生了变化。不用疑惑,我们先来对比一下两者的 不同之处
前者的 setValue() 方法,如下:
public static void setValue(User user) {
user = user.builder().age(15).name(&#34;李四&#34;).build();
System.out.println(&#34;调用时:&#34; + user);
}
后者的 setValue() 方法,如下:
public static void setValue(User user) {
user.setAge(12);
user.setName(&#34;李四&#34;);
System.out.println(&#34;调用时:&#34; + user);
}
其实你会发现,两者的区别很明显:
前者是直接修改了整个 user 的值;而后者,是分别对 user 对象的属性进行重新赋值的
前者的调用过程,我们在上面已经分析过了:由于这里的 user 是 main 方法中 user 对象的拷贝,那么,这里即使重新对这个 user 赋值,并不会更改 main 方法中的 user 的值
我们一起来分析一下后者这段代码的调用过程,如下图所示:
2 分析

说明
- main 方法 将 user 拷贝 给 setValue() 方法
2)拷贝的内容是 user 本身,但是拷贝之后的 user 对象的值,指向的还是与拷贝前的 user 所指向的同一份值
age = 11,
name = &#34;杰哥&#34;
3)那么,在 setValue() 中,修改了这份值,改为了:
age = 12,
name = &#34;李四&#34;
那么,值就这一份,被修改了,那最终呈现出来的效果,就是我们最终的 user 对象就发生了变化
这其实也印证了,java 是值传递的,只是对于 java 中的对象参数来说,值的内容是对象的引用。我们再来回顾一下值传递到底是什么?
值传递 是指在调用函数时将实际参数 复制 一份传递到函数中,而并不是将这个值直接传递给函数
而,我们这里实际上也是把 user 复制了一份到 setValue()函数中,这里的值实际上是 user 的对象引用,只 copy 了引用,也就是地址,但是他们所指向的内容,却依旧是同一份,java 中并不会为你再创建一份,那么,直接修改了这些内容,那么原来的 user 对象的值肯定也就发生了改变
四 总结
好了,事实证明,java 中的确只是存在值传递的,不知道你理解了没有。我们再来总结一下
1 java 中只存在值传递,对于 基本类型、引用类型以及对象类型均是如此,因此调用了一个对某个传递过来的参数进行赋值操作的时候,均不会影响原来的值
2 对于对象类型,若直接修改了它的具体属性,当出现在调用方法之后,会发生改变的原因是:在 java 中,对象类型的值是对象引用,在调用过程中,传递的是一份对象引用的拷贝进行传递的,但是原引用和拷贝的引用依旧指向的是堆中的同一份值,因此,这份值做了改变,原来的 对象类型本身就发生了变化 |
|