網路上能搜尋到的資料裡,關於Java到底是值傳遞還是引用傳遞的討論是比較多的,也沒有一個特別被大家認可的結論。
因為最近一兩年轉到了Golang的開發,接觸到了比較多的指標的玩法,突然對Java的引用傳遞和值傳遞又有了一定的興趣。
但是我無意於討論Java到底是值傳遞還是引用傳遞,我只是記錄一下,避免以後開發的時候踩坑。
為了驗證,我寫了這麼一段程式碼:
private void transInt(int x) {
x += 100;
}
// 這段程式碼的呼叫邏輯如下:
int x = 10;
m.transInt(x);
System.out.println("-----------------after int trans----------------");
System.out.println(x);
這段程式碼不出意外的會列印10,從這一點上看,Java是值傳遞的,因為傳入transInt的是x的一個副本。
不過事情並不會這麼簡單的結束,上面的例子太特殊了,我使用的是Java提供的基本型別。
換成String型別是否還能如此,這是一個值的驗證的問題,所以應該實現這樣一段程式碼:
private void transString(String x) {
x += "bar";
}
// 這段程式碼的呼叫邏輯如下:
String str = "foo";
m.transString(str);
這段程式碼的執行結果是"foo",也就是說非基本型別也是值傳遞。
事情到了這一步似乎是可以說Java是值傳遞了,但是事情並不會這麼簡單的結束,我實現了一個類:
class Solution implements Cloneable {
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;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Solution) super.clone();
}
}
然後實現了一個方法:
private void transfer(Solution solution) {
solution.setAge(12);
solution.setName("lee");
}
// 呼叫方法如下:
Solution solution = new Solution();
solution.setName("foo");
solution.setAge(20);
System.out.println("--------------before transfer------------------------");
System.out.println(solution.getAge());
System.out.println(solution.getName());
m.transfer(solution);
System.out.println("--------------after transfer-------------------");
System.out.println(solution.getAge());
System.out.println(solution.getName());
列印結果如下:
--------------before transfer------------------------
20
foo
--------------after transfer-------------------
12
lee
這種結果很明顯就是引用傳遞了。事到如今又可以說Java是引用傳遞了。
所以不能簡單的說Java是值傳遞還是引用傳遞,這裡的水還是比較深的,如果不是很瞭解,很可能在程式碼實現的時候出現一些奇怪的問題。
這一點上我覺得Java偷感比較重,同樣的如果是Golang,則比較直接。
比如這段程式碼:
package main
import "fmt"
type Student struct {
Name string
Age int
}
func transStu(stu Student) {
stu.Age = 12
stu.Name = "bar"
}
func transStuPoint(stu *Student) {
stu.Age = 100
stu.Name = "lee"
}
func main() {
stu := &Student{
Name: "foo",
Age: 21,
}
fmt.Printf("%s:%d\n", stu.Name, stu.Age)
transStu(*stu)
fmt.Printf("%s:%d\n", stu.Name, stu.Age)
transStuPoint(stu)
fmt.Printf("%s:%d\n", stu.Name, stu.Age)
}
如果傳入的是值,那麼就是值傳遞,如果傳入的是指標,那麼就是引用傳遞,控制權交給程式設計師,所以這段程式碼的列印結果就是:
foo:21
foo:21
lee:100
之前學習C語言的時候,就是因為受不了這麼靈活的指標而轉投Java,但是現在看來,C語言把大部分的控制權交給程式設計師不失為一種明智的選擇。
基本沒見過網上有討論C或者Go是值傳遞還是引用傳遞的。
使用Golang有一點好處就是一下子點開了我的C語言。C語言的好處就是把選擇權交給程式設計師,基本上程式設計師就是程式的王,如果用C語言的話是不存在這種值傳遞還是引用傳遞的爭議的,比如下面的程式碼:
#include<stdio.h>
int main() {
printf("before trans1\n");
int a1 = 10;
printf("input value is %d, input address is %p\n", a1, &a1);
trans1(a1);
printf("after trans1\n");
printf("%d\n", a1);
int a2 = 11;
printf("before trans2\n");
printf("input value is %d, input address is %p\n", a2, &a2);
printf("%d\n", a2);
trans2(&a2);
printf("after trans2\n");
printf("%d\n", a2);
return 0;
}
// 傳入的是一個值
void trans1(int x)
{
// 這裡的列印值應該和main函式里的不一致,因為傳入的是一個全新的數
printf("input x value is %d, input address is %p\n", x, &x);
x += 100;
}
// 傳入的是int型指標
void trans2(int * x)
{
// 這裡應該和外層的列印值是一樣的
printf("input x value is %d, input address is %p\n", *x, x);
// 取值之後,加100,
*x += 100;
}
列印結果:
before trans1
input value is 10, input address is 0x7ffffcc3c
(下一行的地址和上面不同,傳給trans1的實際上是一個全新的值)
input x value is 10, input address is 0x7ffffcc10
after trans1
10
before trans2
input value is 11, input address is 0x7ffffcc38
11
input x value is 11, input address is 0x7ffffcc38
(傳給trans2的實際是一段地址,和上面的列印結果相同)
after trans2
111