1. 程式人生 > >Java中真的只有值傳遞麼?

Java中真的只有值傳遞麼?

Java中真的只有值傳遞麼?

(本文非引戰或diss,只是說出自己的理解,歡迎擺正心態觀看或探討)

回顧值傳遞和引用傳遞

關於Java是值傳遞還是引用傳遞,網上有不一樣的說法。

1、基本型別或基本型別的包裝類以及String是值傳遞,引用型別是引用傳遞。
2、Java中只有值傳遞。

關於這個問題應該是存在爭議的。根據測試出來的結果和我們自己的經驗,以及口口相傳或是上學時老師講的,我們認為是第一種。但第二種說法的呼聲也很高,漸漸地我們也認為第2中才是對的。那麼下面我們就來分析一下這個問題。

在談這個問題之前我們先了解值傳遞和引用傳遞的概念及現象。我還記得,值傳遞和引用傳遞這些概念是大學裡學Java的時候老師教給我的,它們的概念是什麼呢?老師是通過例子來講解的,大概是這樣的。

值傳遞

例子1:

 1public static void main(String[] args){
2    TestJavaParamPass() tjpp = new TestJavaParamPass();
3    int num = 10;
4    tjpp.change(num);
5    System.out.println("num in main():"+i);
6}
7public void change(int param){
8    param = 20;
9    System.out.println("param in change():"+param);
10}

控制檯輸出:

1param in change():10
2num in main():20

mian()方法中的int變數num傳遞給change()方法,change()方法接收到後將值改變為20。通過看控制檯輸出,main()方法中的num變數的值沒有改變。

結論:基本型別是值傳遞。

引用傳遞

例子2:

 1public static void main(String[] args){
2    TestJavaParamPass() tjpp = new TestJavaParamPass();
3    User user = new User();
4    user.setName("Jerry");
5    tjpp.change(user);
6    System.out.println("user in mian():"+user);
7}
8public void change(User param){
9    param.setName("Tom");
10    System.out.println("param in change():"+param);
11}

控制檯輸出:

1param in change():User(name=Tom}
2user in mian():User(name=Tom}

main()方法中的user變數傳遞給change()方法,change()方法改變了其name屬性值。通過看控制檯輸出,main()方法中的user變數的name屬性值發生改變。

結論:引用型別是引用傳遞。

特殊的值傳遞

例子3:

 1public static void main(String[] args){
2    TestJavaParamPass() tjpp = new TestJavaParamPass();
3    String name = "Jerry";
4    tjpp.change(name);
5    System.out.println("name in main():"+i);
6}
7public void change(String param){
8    param = "Tom";
9    System.out.println("param in change():"+param);
10}

控制檯輸出:

1param in change():Tom
2name in mian():Jerry

String也是引用型別的資料型別,為什麼值沒改變?因為在change()方法裡param = "Tom";相當於param = new String("Tom");就相當於param被重新賦值指向了另外一個物件。所以,其實String型別傳的是引用,只不過被重新賦值指向了別的物件了,沒有修改原物件。即,String本質上還是引用傳遞,表像上是值傳遞。

結論:基本型別是值傳遞,引用型別是引用傳遞,String是特殊的值傳遞。

這個結論也是網路上流傳的比較多的,可能大部分程式設計師的認知都是這樣的。至於值傳遞和引用傳遞的概念,接下來便可根據上面的例子和結論推斷出來,以及解釋為什麼大多數程式設計師都將String理解為是特殊的值傳遞。

概念提取

與其叫概念提取好不如叫結論總結呢。

值傳遞:基本型別的變數在被傳遞給方法時,傳遞的是該變數的值(即複製自己的值傳遞給方法)。

引用傳遞:引用型別的變數在被傳遞給方法時, 傳遞的是該變數的引用(即自己所指向的記憶體地址)。

為什麼說String是特殊的值傳遞:是因為String和基本型別從表象來說表現出來的結果是一樣,大概是為了便於記憶這個結果才這樣說的吧。但是要知道String也是引用傳遞只不過它的引用被重新賦值,指向了別的物件了,所以不會影響原值。所以String不能簡單的說是值傳遞。

解析Java只有值傳遞的說法

只有值傳遞的說法

網上還流傳一種說法叫Java只有值傳遞。網上有文章論證了Java只有值傳遞的說法,其中舉的例子和上面的類似。

分析的很透徹,解釋了上面三個例子的本質。指出了上面第二個例子的錯誤之處,舉的例子不恰當。並指出下面這樣的例子才恰當,又舉了鑰匙和房子的例子,佐證了上面第2個例子確實不恰當。因為上面的例子的側重點都是最後實際變數的值有沒有改變。

 1public static void main(String[] args){
2    TestJavaParamPass() tjpp = new TestJavaParamPass();
3    User user = new User();
4    user.setName("Jerry");
5    tjpp.change(user);
6    System.out.println("user in mian():"+user);
7}
8public void change(User param){
9    param = new User()
10    param.setName("Tom");
11    System.out.println("param in change():"+param);
12}

輸出:

1param in change():User(name=Tom}
2user in mian():User(name=Jerry}

最後文章的結論是Java只有值傳遞。引用型別大概是這樣解釋的( 基本型別就不用說了 ),實際變數(實際引數)賦值一份自己的引用地址的值傳給方法,方法的形式引數拿到的是實參的引用地址的值。側重點在值,所以結論說的是引用型別也是值傳遞。

解析

我覺得論證者分析基本型別和引用型別的實參形參的變化的原理是沒有問題的,但是得出的結論是不是有點不恰當。怎麼說呢?請繼續看。

論證者的意思是,java只有值傳遞。也就是說引用型別也是值傳遞,側重點是複製一份引用的地址的值給形參,在於這裡的值是引用的地址的值(不是引用所指向的記憶體裡存的值),所以說是值傳遞。是不是有點牽強?我覺得有點偷換概念,沒錯,大家都知道引用型別傳遞的是引用的值,但你不能因為傳遞的是值就說是值傳遞,不傳值還能傳什麼?引用是記憶體地址,不是也得用值表示麼?

而傳統的說法:基本型別是值傳遞(記憶體裡存東西所代表的值),引用型別是引用傳遞。我覺得這個側重點是:基本型別把值複製一份傳遞過去,引用型別把引用複製一份傳遞過去。側重點是傳遞的東西,基本型別傳遞的東西叫變數的值(變數本身所代表的值),引用型別傳遞的東西叫引用(引用本身的值,即記憶體地址),而非引用所指向的記憶體空間內的值。所以這樣理解引用型別傳遞的是引用也沒問題啊。

所以,Java中基本型別傳遞的是變數所代表的自身的值(記憶體裡存東西所代表的值),是值傳遞;引用型別傳遞的是物件的引用,是引用傳遞;再深一步,引用也是一個確切的值來表示的,或者你把引用看作是物件的值,那也可以說引用型別傳遞的是物件的值,是值傳遞。

文章還說了

無論是值傳遞還是引用傳遞,其實都是一種求值策略(Evaluation strategy)。在求值策略中,還有一種叫做按共享傳遞(call by sharing)。其實Java中的引數傳遞嚴格意義上說應該是按共享傳遞。

按共享傳遞,是指在呼叫函式時,傳遞給函式的是實參的地址的拷貝(如果實參在棧中,則直接拷貝該值)。在函式內部對引數進行操作時,需要先拷貝的地址尋找到具體的值,再進行操作。如果該值在棧中,那麼因為是直接拷貝的值,所以函式內部對引數進行操作不會對外部變數產生影響。如果原來拷貝的是原值在堆中的地址,那麼需要先根據該地址找到堆中對應的位置,再進行操作。因為傳遞的是地址的拷貝所以函式內對值的操作對外部變數是可見的。

簡單點說,Java中的傳遞,是值傳遞,而這個值,實際上是物件的引用。

這裡的意思是,不論是基本型別還是引用型別傳給函式的是實參的地址拷貝,也就是記憶體地址,可以說是引用,只不過基本型別在棧中,函式內對引數操作時直接拷貝的值,引用型別的值在堆中,需要先找到它的位置,即地址、引用。最後說java是值傳遞,而這個值是物件的引用。

看到這明白了麼?

地址就是引用,那是不是可以說java是引用傳遞了?傳遞的是引用的值,計算機中不全是值嗎,不是值還能是什麼,說是引用傳遞是側重點不同傳,傳過去的就是地址就是引用,引用不用值表示用啥

這裡說的值不是一個概念,說基本型別傳的是值,這個是值變數本身的值,說物件傳的也是值,這個值說的是引用是地址,而說物件說是引用傳遞,側重點在於說是傳的地址,指向物件所代表的內部的屬性的地址,非物件所表示的內部的屬性的值,為的是和基本型別直接傳值區分開。

維基百科:引用 (程式設計)

在電腦科學中,引用(英語:reference)是指一個可以讓程式間接訪問於電腦儲存器或其他儲存裝置中一特定資料的值,該資料可以為變數或記錄。
引用和資料本身不同。一般而言,引用會是資料儲存於儲存器或儲存裝置中的實體地址。因此,引用亦常被稱為該資料的指標或地址。

看看引用的定義,引用是指一個XXX資料的值。好吧,引用本身就是一個值。但不是值還能是什麼呢?計算機中不都是值麼?

說值傳遞還是引用傳遞都沒有錯,關鍵是你怎麼定義和解釋值傳遞、引用傳遞的概念以及值所表示的東西。

計算機中一切皆值,如果從這點出發,那全都是傳的值啊,只不過細化到java中,基本型別傳遞的是自身的值,引用型別傳遞的是引用的值,而非物件內屬性的值。

所以如果武斷的說只有值傳遞也是沒問題的,因為在計算機中只能用值來表示啊,但覺得有點投機取巧,就和說世界上只有***,那還區分**和**幹嘛,道理差不多。(暫時想不到好的例子哈哈)

還是剛才說的那句,說是引用傳遞,側重點在於說是傳的是引用是地址,而非物件所表示的內部的屬性值,為的是和基本型別直接傳值區分開,便於記憶.

最後

最後,大家理解現象的原理即可,沒必要追的那麼深,或玩文字遊戲,鑽牛角尖。

如果有人問你,你可以這麼說,基本型別和他們的包裝類是值傳遞,引用型別傳遞的是物件的引用即地址值,String傳遞的也是地址值,只不過在函式內地址值被修改了,所以不會影響到實參,因表現上和基本型別一樣,所以可能為了便於記住這個現象才說String是值傳遞的吧。歸根到底傳的都是值只不過值的含義不同,所以才有Java只有值傳遞的說法吧。

(本文非引戰或diss,只是說出自己的理解,歡迎探討)