Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Latest commit

 

History

History
History
225 lines (156 loc) · 13.4 KB

File metadata and controls

225 lines (156 loc) · 13.4 KB
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# 为什么说Java中只有值传递
对于初学者来说要想把这个问题回答正确是比较难的在第二天整理答案的时候我发现我竟然无法通过简单的语言把这个事情描述的很容易理解遗憾的是我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂所以就有了我写这篇文章的初衷这篇文章中我从什么是方法的实际参数和形式参数开始给你讲解为什么说Java中只有值传递
### 辟谣时间
关于这个问题[StackOverflow][2]上也引发过广泛的讨论看来很多程序员对于这个问题的理解都不尽相同甚至很多人理解的是错误的还有的人可能知道Java中的参数传递是值传递但是说不出来为什么
在开始深入讲解之前有必要纠正一下大家以前的那些错误看法了如果你有以下想法那么你有必要好好阅读本文
> 错误理解一值传递和引用传递区分的条件是传递的内容如果是个值就是值传递如果是个引用就是引用传递
>
> 错误理解二Java是引用传递
>
> 错误理解三传递的参数如果是普通类型那就是值传递如果是对象那就是引用传递
### 实参与形参
我们都知道在Java中定义方法的时候是可以定义参数的比如Java中的main方法,`public static void main(String[] args)`,这里面的args就是参数参数在程序语言中分为形式参数和实际参数
> 形式参数是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数
>
> 实际参数在调用有参函数时主调函数和被调函数之间有数据传递关系在主调函数中调用一个函数时函数名后面括号中的参数称为实际参数”。
简单举个例子
```
public static void main(String[] args) {
ParamTest pt = new ParamTest();
pt.sout("Hollis");//实际参数为 Hollis
}
public void sout(String name) { //形式参数为 name
System.out.println(name);
}
```
实际参数是调用有参方法的时候真正传递的内容而形式参数是用于接收实参内容的参数
### 值传递与引用传递
上面提到了当我们调用一个有参函数的时候会把实际参数传递给形式参数但是在程序语言中这个传递过程中传递的两种情况即值传递和引用传递我们来看下程序语言中是如何定义和区分值传递和引用传递的
> 值传递pass by value是指在调用函数时将实际参数复制一份传递到函数中这样在函数中如果对参数进行修改将不会影响到实际参数
>
> 引用传递pass by reference是指在调用函数时将实际参数的地址直接传递到函数中那么在函数中对参数所进行的修改将影响到实际参数
有了上面的概念然后大家就可以写代码实践了来看看Java中到底是值传递还是引用传递于是最简单的一段代码出来了
```
public static void main(String[] args) {
ParamTest pt = new ParamTest();
int i = 10;
pt.pass(i);
System.out.println("print in main , i is " + i);
}
public void pass(int j) {
j = 20;
System.out.println("print in pass , j is " + j);
}
```
上面的代码中我们在pass方法中修改了参数j的值然后分别在pass方法和main方法中打印参数的值输出结果如下
```
print in pass , j is 20
print in main , i is 10
```
可见pass方法内部对name的值的修改并没有改变实际参数i的值那么按照上面的定义有人得到结论Java的方法传递是值传递
但是很快就有人提出质疑了哈哈所以不要轻易下结论咯。)。然后他们会搬出以下代码
```
public static void main(String[] args) {
ParamTest pt = new ParamTest();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
```
同样是一个pass方法同样是在pass方法内修改参数的值输出结果如下
```
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='hollischuang', gender='Male'}
```
经过pass方法执行后实参的值竟然被改变了那按照上面的引用传递的定义实际参数的值被改变了这不就是引用传递了么于是根据上面的两段代码有人得出一个新的结论Java的方法中在传递普通类型的时候是值传递在传递对象类型的时候是引用传递
但是这种表述仍然是错误的不信你看下面这个参数类型为对象的参数传递
```
public static void main(String[] args) {
ParamTest pt = new ParamTest();
String name = "Hollis";
pt.pass(name);
System.out.println("print in main , name is " + name);
}
public void pass(String name) {
name = "hollischuang";
System.out.println("print in pass , name is " + name);
}
```
上面的代码输出结果为
```
print in pass , name is hollischuang
print in main , name is Hollis
```
这又作何解释呢同样传递了一个对象但是原始参数的值并没有被修改难道传递对象又变成值传递了
### Java中的值传递
上面我们举了三个例子表现的结果却不一样这也是导致很多初学者甚至很多高级程序员对于Java的传递类型有困惑的原因
其实我想告诉大家的是上面的概念没有错只是代码的例子有问题我再来给大家画一下概念中的重点然后再举几个真正恰当的例子
> 值传递pass by value是指在调用函数时将实际参数`复制`一份传递到函数中这样在函数中如果对`参数`进行修改将不会影响到实际参数
>
> 引用传递pass by reference是指在调用函数时将实际参数的地址`直接`传递到函数中那么在函数中对`参数`所进行的修改将影响到实际参数
那么我来给大家总结一下值传递和引用传递之前的区别的重点是什么
[<img src="http://www.hollischuang.com/wp-content/uploads/2018/04/pass.jpg" alt="pass" width="474" height="73" class="aligncenter size-full wp-image-2289" />][3]
我们上面看过的几个pass的例子中都只关注了实际参数内容是否有改变如传递的是User对象我们试着改变他的name属性的值然后检查是否有改变其实在实验方法上就错了当然得到的结论也就有问题了
为什么说实验方法错了呢这里我们来举一个形象的例子再来深入理解一下值传递和引用传递然后你就知道为啥错了
你有一把钥匙当你的朋友想要去你家的时候如果你`直接`把你的钥匙给他了这就是引用传递这种情况下如果他对这把钥匙做了什么事情比如他在钥匙上刻下了自己名字那么这把钥匙还给你的时候你自己的钥匙上也会多出他刻的名字
你有一把钥匙当你的朋友想要去你家的时候`复刻`了一把新钥匙给他自己的还在自己手里这就是值传递这种情况下他对这把钥匙做什么都不会影响你手里的这把钥匙
但是不管上面那种情况你的朋友拿着你给他的钥匙进到你的家里把你家的电视砸了那你说你会不会受到影响而我们在pass方法中改变user对象的name属性的值的时候不就是在砸电视
还拿上面的一个例子来举例我们`真正的改变参数`,看看会发生什么
```
public static void main(String[] args) {
ParamTest pt = new ParamTest();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user = new User();
user.setName("hollischuang");
user.setGender("Male");
System.out.println("print in pass , user is " + user);
}
```
上面的代码中我们在pass方法中改变了user对象输出结果如下
```
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='Hollis', gender='Male'}
```
我们来画一张图看一下整个过程中发生了什么然后我再告诉你为啥Java中只有值传递
[<img src="http://www.hollischuang.com/wp-content/uploads/2018/04/pass1.png" alt="pass1" width="859" height="721" class="aligncenter size-full wp-image-2293" />][4]
稍微解释下这张图当我们在main中创建一个User对象的时候在堆中开辟一块内存其中保存了name和gender等数据然后hollis持有该内存的地址`0x123456`(图1)。当尝试调用pass方法并且hollis作为实际参数传递给形式参数user的时候会把这个地址`0x123456`交给user这时user也指向了这个地址图2)。然后在pass方法内对参数进行修改的时候`user = new User();`,会重新开辟一块`0X456789`的内存赋值给user后面对user的任何修改都不会改变内存`0X123456`的内容图3)。
上面这种传递是什么传递肯定不是引用传递如果是引用传递的话`user=new User()`的时候实际参数的引用也应该改为指向`0X456789`,但是实际上并没有
通过概念我们也能知道这里是把实际参数的引用的地址复制了一份传递给了形式参数所以,**上面的参数其实是值传递把实参对象引用的地址当做值传递给了形式参数。**
我们再来回顾下之前的那个砸电视的例子看那个例子中的传递过程发生了什么
[<img src="http://www.hollischuang.com/wp-content/uploads/2018/04/pass21.png" alt="pass2" width="832" height="732" class="aligncenter size-full wp-image-2307" />][5]
同样的在参数传递的过程中实际参数的地址`0X1213456`被拷贝给了形参只是在这个方法中并没有对形参本身进行修改而是修改的形参持有的地址中存储的内容
所以值传递和引用传递的区别并不是传递的内容而是实参到底有没有被复制一份给形参在判断实参内容有没有受影响的时候要看传的的是什么如果你传递的是个地址那么就看这个地址的变化会不会有影响而不是看地址指向的对象的变化就像钥匙和房子的关系
那么既然这样为啥上面同样是传递对象传递的String对象和User对象的表现结果不一样呢我们在pass方法中使用`name = "hollischuang";`试着去更改name的值阴差阳错的直接改变了name的引用的地址因为这段代码会new一个String在把引用交给name即等价于`name = new String("hollischuang");`。而原来的那个"Hollis"字符串还是由实参持有着的所以并没有修改到实际参数的值
[<img src="http://www.hollischuang.com/wp-content/uploads/2018/04/pass3.png" alt="pass3" width="515" height="399" class="aligncenter size-full wp-image-2311" />][6]
**所以说Java中其实还是值传递的只不过对于对象参数值的内容是对象的引用。**
### 总结
无论是值传递还是引用传递其实都是一种求值策略([Evaluation strategy][7])。在求值策略中还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递
> 按共享传递是指在调用函数时传递给函数的是实参的地址的拷贝如果实参在栈中则直接拷贝该值)。在函数内部对参数进行操作时需要先拷贝的地址寻找到具体的值再进行操作如果该值在栈中那么因为是直接拷贝的值所以函数内部对参数进行操作不会对外部变量产生影响如果原来拷贝的是原值在堆中的地址那么需要先根据该地址找到堆中对应的位置再进行操作因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的
简单点说Java中的传递是值传递而这个值实际上是对象的引用
而按共享传递其实只是按值传递的一个特例罢了所以我们可以说Java的传递是按共享传递或者说Java中的传递是值传递
### 参考资料
[Evaluation strategy][7]
[关于值传递和引用传递][8]
[按值传递按引用传递按共享传递][9]
[Is Javapass-by-referenceorpass-by-value”?][2]
[1]: http://www.hollischuang.com/wp-content/uploads/2018/04/question.png
[2]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
[3]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass.jpg
[4]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass1.png
[5]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass21.png
[6]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass3.png
[7]: https://en.wikipedia.org/wiki/Evaluation_strategy
[8]: http://chenwenbo.github.io/2016/05/11/%E5%85%B3%E4%BA%8E%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92/
[9]: http://menzhongxin.com/2017/02/07/%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92-%E6%8C%89%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92%E5%92%8C%E6%8C%89%E5%85%B1%E4%BA%AB%E4%BC%A0%E9%80%92/
Morty Proxy This is a proxified and sanitized view of the page, visit original site.