大家好,我是 杰哥
还记不记得,刚开始找工作的时候,笔试题中,往往会有那么几道代码题是这样的:给你几行代码,让你判断输出的结果是什么。经常出现的是判断程序启动时,父类子类的构造方法、静态方法、静态属性与普通方法等的执行顺序。还有一种类型则是,一个值,调用了一个 void 方法之后,判断打印出来的是修改以后的值是什么,一般都是考你经过这个方法的调用,这个值会不会发生变化
这些套路题,往往在最初会难倒很多基础较为薄弱的童鞋,但是若搞懂了这些背后的原理,那你还怕什么,这些对你来说,岂不是送分题?推你进大厂呢?
那么,今天我们就来一起看看,java
中的值传递与引用传递,消灭以前的一些错误理解,彻底搞清楚 java 在调用中,究竟是如何进行参数的传递的?
一 理论先行
要搞清楚 java
中的方法调用,到底是值传递,还是引用传递之前,先来看看 值传递
和引用传递
分别是什么
值传递
(pass by value)是指在调用函数时将实际参数 复制
一份传递到函数中, 而并不是将这个值直接传递给函数
引用传递
(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,而传递过来的地址还是与之前的地址指向同一个值,那么要是修改了这个参数,就会影响这个值的改变
二 实践寻找
很多人都说 Java
中实际上是只有值传递
,其实,我也很认同,并通过以下几个例子,做了验证
看的时候,可以先别看答案,先自己猜一猜会是什么结果哦
1 传递类型为基本类型 int
打印结果:
调用时:20
调用后:10
说明
value
的值并没有因为调用了 setValue()
方法,而发生改变
也就是说,我们验证了 java 中的 基本类型 是采用值传递的方式的
2 传递类型为引用类型: String
打印结果:
调用时:hello123
调用后:hello
说明
value
的值并没有因为调用了 setValue()
方法,而发生改变
那么,我们也验证了 java 中的 引用类型
是采用值传递的方式的
中场休息
来来来,那么也就是说,我们基本上可以下个结论,就是说:java
的确如大家所说,在方法调用过程中,是采用值传递的
我们来分析一下他们调用过程中的步骤
我们知道,java
在进行方法调用时,会将线程中每个方法分别放入栈中,先进入的方法,最终被放在了最低层,因此会在最后被执行,最后入栈的方法,则会最先被执行
那么,对于 main
方法和 setValue()
方法,在 栈中存放的 方式如下:
main
方法 中的 value,被传递给 setValue()
方法,最终它的值并没有改变,也就是说,它是值传递:即传递 给 setValue()
的参数,是 value
的副本,而并不是 value
本身,所以,我们在 setValue()
方法中,即时为 value
重新赋值,由于它是另一个 value
,那么,原来的 value
值当然不会被修改了
也许,你会对此提出质疑,因为你在想,即使 基本类型 和这里的 String 类型,虽然被印证了是采用值传递的,但是要是其他引用类型呢?比如说 一个实体类对象的值,往往就因为调用方法,而发生变化
是这样吗?我们来接着看看
3 传递类型为引用类型:User
打印结果:
调用时:User(id=0, name=李四, age=15)
调用后:User(id=0, name=杰哥, age=11)
说明
value
的值并没有因为调用了 setValue()
方法,而发生改变。杰哥,还是那个杰哥
这下你相信了吧?引用类型,也是采用了值传递的:在方法调用过程中,只会传递一份参数的副本,因此并不会影响原来的值
如果没有完全说服你,那么我们再来一个例子,传递类型为集合
4 传递类型为 集合:List
打印结果:
调用时:[world, hello]
调用后:[hello, world]
说明:
并且将 list
换成 List<User>
类型的结果也一模一样,调用方法之后的 list
值并不会发生变化。这下,我猜你应该已经确信了,java 的确是值传递的,嗯,没毛病
但是,估计有一少部分人,还是会举出一个反例,来推倒这个理论,为了加深大家的理解,我们也一起来看看 这个所谓的 ” 反例 “
三 反例说明
先来猜猜打印结果。。。。。。
是的,打印结果如下:
调用时:User(id=0, name=李四, age=12)
调用后:User(id=0, name=李四, age=12)
说明:
首先,user
的值,在调用了 setValue()
方法之后,是被修改了的,怎么样,是不是会有一点点蒙?
哈哈,没有关系,往下看
1 对比
这个可以跟第二章中的第 3 个例子,对比着看,同样是 User
对象的传递,最终前者未发生变化,而后者却发生了变化。不用疑惑,我们先来对比一下两者的 不同之处
前者的 setValue()
方法,如下:
后者的 setValue()
方法,如下:
其实你会发现,两者的区别很明显:
前者是直接修改了整个 user
的值;而后者,是分别对 user
对象的属性进行重新赋值的
前者的调用过程,我们在上面已经分析过了:由于这里的 user
是 main
方法中 user
对象的拷贝,那么,这里即使重新对这个 user
赋值,并不会更改 main
方法中的 user
的值
我们一起来分析一下后者这段代码的调用过程,如下图所示:
2 分析
说明
main
方法 将user
拷贝 给setValue()
方法
2)拷贝的内容是 user
本身,但是拷贝之后的 user
对象的值,指向的还是与拷贝前的 user
所指向的同一份值(!!!!!!浅拷贝,而不是深拷贝?)
age = 11,
name = "杰哥"
3)那么,在 setValue()
中,修改了这份值,改为了:
age = 12,
name = "李四"
那么,值就这一份,被修改了,那最终呈现出来的效果,就是我们最终的 user
对象就发生了变化
这其实也印证了,java
是值传递的,只是对于 java
中的对象参数来说,值的内容是对象的引用。我们再来回顾一下值传递到底是什么?
值传递
是指在调用函数时将实际参数 复制
一份传递到函数中, 而并不是将这个值直接传递给函数
而,我们这里实际上也是把 user
复制了一份到 setValue()
函数中,这里的值实际上是 user
的对象引用,只 copy
了引用,也就是地址,但是他们所指向的内容,却依旧是同一份,java 中并不会为你再创建一份,那么,直接修改了这些内容,那么原来的 user
对象的值肯定也就发生了改变
四 总结
好了,事实证明,java
中的确只是存在值传递的,不知道你理解了没有。我们再来总结一下
1 java
中只存在值传递,对于 基本类型、引用类型以及对象类型均是如此,因此调用了一个对某个传递过来的参数进行赋值操作的时候,均不会影响原来的值
2 对于对象类型
,若直接修改了它的具体属性,当出现在调用方法之后,会发生改变的原因是:在 java
中,对象类型的值是对象引用
,在调用过程中,传递的是一份对象引用的拷贝
进行传递的,但是原引用和拷贝的引用依旧指向的是堆中的同一份值
,因此,这份值做了改变,原来的 对象类型本身就发生了变化