Java的值傳遞和引用傳遞

wingsless發表於2024-08-15

網路上能搜尋到的資料裡,關於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

相關文章