1. 程式人生 > >JAVA中只有值傳遞

JAVA中只有值傳遞

http 自己的 分享 傳遞 uil string 論戰 顯示 主函數

今天,我在一本面試書上看到了關於java的一個參數傳遞的問題:

寫道 java中對象作為參數傳遞給一個方法,到底是值傳遞,還是引用傳遞?

我毫無疑問的回答:“引用傳遞!”,並且還覺得自己對java的這一特性很是熟悉!

結果發現,我錯了!

答案是:

值傳遞!Java中只有按值傳遞,沒有按引用傳遞!

回家後我就迫不及待地查詢了這個問題,覺得自己對java這麽基礎的問題都搞錯實在太丟人!

綜合網上的描述,我大概了解了是怎麽回事,現在整理如下,如有不對之處望大神提出!

先來看一個作為程序員都熟悉的值傳遞的例子:

Java代碼 技術分享
  1. ... ...
  2. //定義了一個改變參數值的函數
  3. public static void changeValue(int x) {
  4. x = x *2;
  5. }
  6. ... ...
  7. //調用該函數
  8. int num = 5;
  9. System.out.println(num);
  10. changeValue(num);
  11. System.out.println(num);
  12. ... ...

答案顯而易見,調用函數changeValue()前後num的值都沒有改變。

由此做一個引子,我用圖表描繪一個值傳遞的過程:

技術分享

num作為參數傳遞給changeValue()方法時,是將內存空間中num所指向的那個存儲單元中存放的值,即"5

",傳送給了changeValue()方法中的x變量,而這個x變量也在內存空間中分配了一個存儲單元,這個時候,就把num的值5傳送給了這個存儲單元中。此後,在changeValue()方法中對x的一切操作都是針對x所指向的這個存儲單元,與num所指向的那個存儲單元沒有關系了!

自然,在函數調用之後,num所指向的存儲單元的值還是沒有發生變化,這就是所謂的“值傳遞”!值傳遞的精髓是:傳遞的是存儲單元中的內容,而非地址或者引用!

接下來,就來看java中的對象參數是怎麽傳遞的:

同樣,先給出一段代碼:

Java代碼 技術分享
  1. ... ...
  2. class person {
  3. public static String name = "Jack";
  4. ... ...
  5. }
  6. ... ...
  7. //定義一個改變對象屬性的方法
  8. public static void changeName(Person p) {
  9. p.name = "Rose";
  10. }
  11. ... ...
  12. public static void main(String[] args) {
  13. //定義一個Person對象,person是這個對象的引用
  14. Person person = new Person();
  15. //先顯示這個對象的name屬性
  16. System.out.println(person.name);
  17. //調用changeName(Person p)方法
  18. changeName(person);
  19. //再顯示這個對象的name屬性,看是否發生了變化
  20. System.out.println(person.name);
  21. }

答案應該大家都心知肚明:

第一次顯示:“Jack”

第二次顯示:“Rose”

方法用了一個對象參數,該對象內部的內容就可以改變,我之前一直認為應該是該對象復制了一個引用副本給調用函數的參數,使得該方法可以對這個對象進行操作,其實是錯了!

http://www.cnblogs.com/clara/archive/2011/09/17/2179493.html 寫道 Java 編程語言只有值傳遞參數。當一個對象實例作為一個參數被傳遞到方法中時,參數的值就是該對象的引用一個副本。指向同一個對象,對象的內容可以在被調用的方法中改變,但對象的引用(不是引用的副本)是永遠不會改變的。

為什麽這裏是“值傳遞”,而不是“引用傳遞”?

我還是用圖表描繪比較能解釋清楚:

技術分享

主函數中new 了一個對象Person,實際分配了兩個對象:新創建的Person類的實體對象,和指向該對象的引用變量person。

【註意:在java中,新創建的實體對象在堆內存中開辟空間,而引用變量在棧內存中開辟空間】

正如如上圖所示,左側是堆空間,用來分配內存給新創建的實體對象,紅色框是新建的Person類的實體對象,000012是該實體對象的起始地址;而右側是棧空間,用來給引用變量和一些臨時變量分配內存,新實體對象的引用person就在其中,可以看到它的存儲單元的內容是000012,記錄的正是新建Person類實體對象的起始地址,也就是說它指向該實體對象。

這時候,好戲上臺了:

調用了changeName()方法,person作為對象參數傳入該方法,但是大家特別註意,它傳入的是什麽!!!person引用變量將自己的存儲單元的內容傳給了changeName()方法的p變量!也就是將實體對象的地址傳給了p變量,從此,在changeName()方法中對p的一切操作都是針對p所指向的這個存儲單元,與person引用變量所指向的那個存儲單元再沒有關系了!

回顧一下上面的一個值傳遞的例子,值傳遞,就是將存儲單元中的內容傳給調用函數中的那個參數,這裏是不是異曲同工,是所謂“值傳遞”,而非“引用傳遞”!!!

那為什麽對象內部能夠發生變化呢?

那是因為:p所指向的那個存儲單元中的內容是實體對象的地址,使得p也指向了該實體對象,所以才能改變對象內部的屬性!

這也是我們大多數人會誤以為是“引用傳遞”的終極原因!!!

作者:Intopass
鏈接:https://www.zhihu.com/question/31203609/answer/50992895
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。

否則很容易陷入所謂的“一切傳引用其實本質上是傳值”這種並不能解決問題無意義論戰中。
更何況,要想知道Java到底是傳值還是傳引用,起碼你要先知道傳值和傳引用的準確含義吧?可是如果你已經知道了這兩個名字的準確含義,那麽你自己就能判斷Java到底是傳值還是傳引用。
這就好像用大學的名詞來解釋高中的題目,對於初學者根本沒有任何意義。

一:搞清楚 基本類型 和 引用類型的不同之處
int num = 10;
String str = "hello";

技術分享

如圖所示,num是基本類型,值就直接保存在變量中。而str是引用類型,變量中保存的只是實際對象的地址。一般稱這種變量為"引用",引用指向實際對象,實際對象中保存著內容。

二:搞清楚賦值運算符(=)的作用
num = 20;
str = "java";
技術分享

對於基本類型 num ,賦值運算符會直接改變變量的值,原來的值被覆蓋掉。
對於引用類型 str,賦值運算符會改變引用中所保存的地址,原來的地址被覆蓋掉。但是原來的對象不會被改變(重要)。
如上圖所示,"hello" 字符串對象沒有被改變。(沒有被任何引用所指向的對象是垃圾,會被垃圾回收器回收)

三:調用方法時發生了什麽?參數傳遞基本上就是賦值操作
第一個例子:基本類型
void foo(int value) {
    value = 100;
}
foo(num); // num 沒有被改變

第二個例子:沒有提供改變自身方法的引用類型
void foo(String text) {
    text = "windows";
}
foo(str); // str 也沒有被改變

第三個例子:提供了改變自身方法的引用類型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder.append("4");
}
foo(sb); // sb 被改變了,變成了"iphone4"。

第四個例子:提供了改變自身方法的引用類型,但是不使用,而是使用賦值運算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}
foo(sb); // sb 沒有被改變,還是 "iphone"。

重點理解為什麽,第三個例子和第四個例子結果不同?

下面是第三個例子的圖解:
技術分享builder.append("4")之後
技術分享下面是第四個例子的圖解:
技術分享
builder = new StringBuilder("ipad"); 之後
技術分享

http://guhanjie.iteye.com/blog/1683637

作者:Intopass
鏈接:https://www.zhihu.com/question/31203609/answer/50992895
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。

JAVA中只有值傳遞