一切皆按值傳遞

Java 中只有按值傳遞

  "Java 中只有按值傳遞",做爲初學者初看到這幾個字有點不敢相信,無數次經過函數改變過對象,無數次跟同事說 Java 在傳對象的時候是按引用傳遞。後來細細想一想,之因此覺得 Java 傳對象是按引用傳遞是由於其中有不少概念都沒理清楚,與 C++ 中的搞混了。從 C++ 轉 Java 的時候將 C++ 中的知識點映射到 Java 沒錯,這有利於 C++ 轉 Java 的人更快的學習 Java。但一旦映射錯誤就很容易造成固定思惟。java

  在 C++ 和 Java 中都有引用的概念,但他們徹底不是同一個東西。Java 中的引用更相似 C++ 的指針,C++ 的引用在 Java 中並沒有對應概念。在 C++ 中有按值傳遞、按指針傳遞和按引用傳遞三種,而在 Java 中沒有 C++ 引用和指針的概念,只保留了按值傳遞。程序員

  爲了更好的說明 Java 中只有按值傳遞,先來看看 Java 的數據類型,Java 的數據類型分爲基本數據類型和引用類型,其中:架構

  • 基本類型包括 byte/short/int/long/float/double/char/boolean 八種,基本類型在內存中地址中保存的即自己的值,其通常都在棧上分配。函數

  • 引用類型指向一個對象,它與 C++ 的指針很是類似。但 C++ 的指針能夠指向基本類型和類對象,而 Java 的引用只能指向類(枚舉、接口等)對象。Java 中對象自己在堆上分配,而引用類型在棧上分配,其內存地址中保存的是對象在堆中的地址。兩種類型在內存中的佈局以下:佈局

  上圖能夠一目瞭然的看出基本類型與引用類型的區別,基本類型數據即自己,引用類型僅僅是引用。下面經過一個例子來看下基本類型和引用類型在參數傳遞中所表現的不一樣:

1 class MyInteger {
 2     int value;
 3 }
 4 
 5 public class TestReference {
 6 
 7     public static void changeBasic(int arg) {
 8         arg = 2;
 9     }
10 
11     public static void changeReference(MyInteger arg) {
12         arg.value = 2;
13     }
14 
15     public static void main(String[] args) {
16 
17         int basicTypeA = 1;
18 
19         MyInteger referenceTypeA = new MyInteger();
20         referenceTypeA.value = 1;
21 
22         System.out.println("調用 changeBasic 以前 basicTypeA 的值 "+ basicTypeA);
23         changeBasic( basicTypeA);
24         System.out.println("調用 changeBasic 以後 basicTypeA 的值 "+ basicTypeA);
25 
26         System.out.println("調用 changeReference 以前 referenceTypeA 的值 "+ referenceTypeA.value);
27         changeReference( referenceTypeA);
28         System.out.println("調用 changeReference 以後 referenceTypeA 的值 "+ referenceTypeA.value);
29     }
30 }
複製代碼

  運行結果以下:性能

  能夠看出基本類型 int 的變量 basicTypeA 在 changeBasic 調用後值並無發生改變,而引用類型 MyInteger 的變量 referenceTypeA 在調用 changeReference 後發生了改變。這裏就比較容易誤導讀者覺得:Java 基本類型是按值傳遞而引用類型是按引用傳遞(暫且這麼定義)。

  其實否則,按值傳遞的意思想必你們都知道:傳遞的是值的的拷貝,好比上面代碼中的調用 changeBasic(basicTypeA) 時,arg 是 basicTypeA 的一個拷貝,因此不管對 arg 作任何操做都不影響 basicTypeA 變量自己。而調用 changeReference(referenceTypeA) ,arg 也是 referenceTypeA 的一個拷貝,可是因爲 arg 和 referenceTypeA 都是引用類型且他們指向同一對象,因此經過 arg 修改對象,referenceTypeA 也能看到。兩種類型變量在內存中調用過程以下:學習

  因此能夠看出不管是基本類型仍是引用類型,都是按值傳遞。只是因爲它們在內存中所表示的內容不一樣,最後表現出來的結果也有所不一樣。同理,在 C++ 中的按值傳遞、按指針傳遞和按引用傳遞理論上均可以歸爲按值傳遞(其實這個歸類在學 C++ 的時候就概括出來了,只是後來反而忘了)。spa

對"引用"進行按值傳遞的坑

  Java 的引用相似於 C++ 的指針,可是 C++ 的對象(不包括基本類型)傳遞提供了對象自己直接傳遞和指針傳遞兩種方式(引用方式不談),而 Java 對象只有對引用進行傳遞這一種,不存在直接將對象自己進行傳遞。設計

  • 對象自己進行傳遞的好處是傳遞的都是對象的拷貝,在函數中對拷貝的對象作任何修改都不會改變原對象。可是若是傳遞對象很是大,並且調用很頻繁會影響性能。
  • 對象的引用(或者指針)傳遞的好處是隻須要拷貝一個引用(或者指針)大小的數據便可,且能夠在調用的函數中改變原對象內容。缺點是容易挖坑。

  在 C++ 中以上兩種傳遞方式能夠自行選擇,而 Java 裏面只有第 2 種方式,凡事有利有弊,有時候咱們並不想在函數中改變原對象的內容,這裏我就踩過一個坑,咱們的項目中有個對象經過管道傳遞的流程以下:3d

  funcA 與 funcB 是兩個不一樣的人負責的,一次升級後 funcB 的負責人發如今函數中獲取的對象 X 內容不對,一開始還覺得是傳遞對象 X 的接口出現了錯誤即是一頓排查,知道最後才發現對象 X 是在升級後在 funcA 中被修改了,浪費了很多時間。固然這個架構的流程設計的不合理是主要緣由(只須要在分發的時候講對象 X 作手動拷貝便可避免上述問題),可是不不影響咱們拋出 Java 只能對引用進行傳遞的弊端。

  在調用鏈較長、各類 for/while 循環中很容易就犯了上述錯誤,解決方案固然就是手動拷貝對象,Java 中拷貝對象有如下兩種方式:

  • 實現Cloneable接口並重寫Object類中的clone()方法。
  • 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆。

  其中第二種方式能避免深淺拷貝的問題,但調用比較耗時。第一種也能避免深淺拷貝可是須要本身手動去寫相應的代碼,若是嵌套較深,代碼將很是複雜。至於深淺拷貝的問題能夠自行百度,其本質仍是由於只是將對象的引用進行了傳遞而致使的一些問題。記得關注公衆號哦,記錄着一個 C++ 程序員轉 Java 的學習之路。

相關文章
相關標籤/搜索