用畫小狗的方法來解釋Java中的值傳遞
本文首發於我的個人部落格 —— ,轉載請標明出處。
前言
在開始看我畫小狗之前,我們們先來看道很簡單的題目:
下面程式的輸出是什麼?
Dog myDog = new Dog("旺財"); changeName(myDog); System.out.println(myDog.getName());public void changeName(Dog dog) { dog.setName("小強"); }
如果你的回答是“小強”,好,恭喜你答對了。下面我們改一下程式碼:
Dog myDog = new Dog("旺財"); changeName(myDog); System.out.println(myDog.getName());public void changeName(dog) { dog = new Dog(); dog.setName("小強"); }
是的,我只是在changeName方法裡面加了一句程式碼
dog = new Dog();
這一次的輸出又是什麼呢?
A旺財
B小強
答案是 A旺財,changeName方法並沒有把myDog的名稱改了。如果你答錯了,沒關係,我要開始畫小狗了,畫完你就明白了;如果你答對了,但不太明白其中的原因,那我畫的小狗也肯定能幫到你。
myDog是什麼
首先你要搞懂,程式碼裡的變數myDog是什麼?myDog真的就是一隻狗嗎?不!不是!myDog只是一條遛狗用的狗繩!
single-dog.jpg
換句話說說,myDog並不是new出來的放在堆中的物件(object)!myDog只是一個指向這個物件例項的引用(reference)!如果你對足夠了解,應該知道,這個引用是放在上的。
引數傳遞
現在你知道了,myDog只是一條繩子,但這似乎並不能解釋為什麼changeName方法沒有把myDog的名稱改為“小強”,因為按照現有的理解,dog = new Dog(),就是把我的狗繩綁到另一隻小狗身上,然後給這隻小狗起名為“小強”,就像這樣:
wrong-case.jpg
可事實是,myDog還是叫旺財,這是為什麼?
問題就出在方法呼叫上,當我執行changeName(myDog)這一行程式碼時,myDog這條狗繩,被複制了一份,而傳入到changeName方法裡的那條狗繩(dog),就是複製出來的那一條,就像這樣:
copy.jpg
接著執行dog= new Dog(),這一行程式碼,就是把複製出來的那一條狗繩,從myDog解綁,重新綁到new出來的那隻小狗上,也就是後來被起名為“小強”的小狗:
new-instance.jpg
而myDog還是綁在旺財身上,這也就解釋了,為什麼執行完方法出來,myDog.getName()還是旺財。而在第一段程式碼裡面,我們沒有執行dog= new Dog(),也就沒有改變dog所綁的小狗,dog還是綁在旺財身上,因此dog.setName("小強") 就把旺財的名字改成小強了。
string的例子
我們再來看一個例子:
String str = "aaa"; changeString(str); System.out.println(str);public void changeString(String str) { str = "bbb"; }
如果你弄懂了上面那個例子,那麼這裡應該不難理解,changeString方法裡,只是將新複製出來的引用str,指向另外一個字串常量物件“bbb”,方法體外面的str並不受影響,還是指向字串常量“aaa”,因此最終列印的還是aaa.
int的例子
上面提到的都是物件,下面看一個基本資料型別的例子
int i = 1; changeInt(i); System.out.println(i);public void changeInt(int i) { i = 2; }
對於基本資料型別,他們沒有引用,但是不要忘了,呼叫函式時,複製的動作還是會做的,執行changeInt(i)時,會將 i 複製到一個新的int上,傳給changeInt方法,因此不管changeInt內部對入參做了什麼,外面的 i 都不會受影響。最後列印出來的還是1.
值傳遞和引用傳遞
上面提到的引數傳遞過程中的複製操作,說白了,就是 = 操作。把上面那個int例子,做一下方法內聯,其實就是這樣:
int i = 1;// 方法內聯,相當於執行changeInt方法int j = i; // 新建一個和i一樣的變數j = 2; //修改j的值,i不變System.out.println(i);
對於基本資料型別,= 操作將右邊的變數(R_VALUE)完整的複製給左邊的變數(L_VALUE),而對於物件,準確的說,應該是指向物件的引用(就像上面說的myDog),= 操作同樣也是將右邊的引用完整的複製給左邊的引用,兩者指向同一個物件例項。
這個 = 操作,是值傳遞和引用傳遞的根本差別,這也導致了值傳遞和引用傳遞有以下直觀上的差別:
如果引數是值傳遞,那麼呼叫者(方法體外部)和被呼叫者(方法體內部)用的是兩個不同的變數,方法體裡面對變數的改動不會影響方法體外面的變數。而之所以在Java可以在方法體內部改變方法體外部的物件,是因為方法體內部拿到了物件的引用,但是這個引用是和方法體外部的引用屬於兩個不同的引用的,方法體內部的引用指向別的物件,不會導致方法體外部的引用也指向別的物件。
如果引數是引用傳遞,那麼呼叫者(方法體外部)和被呼叫者(方法體內部)用的是兩個相同的變數,方法體裡面對變數的改動會影響方法體外面的變數。
Java的變數都不是物件
透過上面的講解,你也知道了一個很重要的點:Java裡面的變數,要麼是基本資料型別,要麼是指向物件例項的引用型別(狗繩),絕對不會是一個物件(狗)。
狗繩和垃圾回收
弄懂了myDog只是一條狗繩(引用),也有助於我們理解Java的垃圾回收機制,我在裡提到過,一旦JVM發現一個物件跟GC Roots不可達時,這個物件就會被回收掉,看一下下面這段程式碼:
Dog dog = new Dog(); dog = null;
現在我們知道,dog=null就等於是把狗繩給咔嚓減掉了,這樣狗就跑了,變成流浪狗了,就像Java中的物件被當做垃圾回收了一樣:
接著再來看一下交叉引用的例子:
Dog dog1 = new Dog(); Dog dog2 = new Dog(); dog1.son = dog2; dog2.father = dog1; dog1 = null; dog2 = null;
如果JVM採用的是,那麼狗2原先被dog2和dog1.son兩個變數引用這,執行完dog2 = null之後,還被dog1.son引用,狗2是不會被回收的。
但是如果使用,我們就會發現,這兩隻狗和這個世界已經沒有關聯了,儘管他們倆還是父子關係,JVM對於這種互相引用,但是和GC ROOTS已經沒有關聯的物件,照樣會進行回收。
引用傳遞的替代方法
引用傳遞有兩個好處:
引用傳遞可以避免呼叫方法時進行複製,尤其是當方法的入參是個大物件時,複製會耗費大量的時間和空間,當然,這一點Java已經巧妙地解決了,因為對於物件,複製的只是它的引用而已;
引用傳遞可以對外面的物件進行修改,這也是很多語言支援引用傳遞的原因。
那麼,在Java,要怎麼實現“對外面的物件進行修改”類似的功能呢?
答案是使用返回值,類似這樣:
a = doSomeThing(a);
當然,如果你只是對一個物件進行修改,然後返回這個物件的新的版本,那麼可以考慮把這個方法挪到這個物件裡面去,就像這樣:
a = a.doSomeThing();
還有,如果你是需要返回多個值,不使用引用傳遞,要如何實現?
答案是返回一個物件,比如你想修改一個地方的經度和緯度,那麼與其傳入log和lat兩個變數,不如把他們封裝到Point物件裡面去。
本文示例中的完整程式碼,可以到"Bridge for You"的上下載。
以上,希望對你有所幫助。
參考內容
作者:SexyCode
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1020/viewspace-2809730/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 關於值傳遞和引用傳遞的解釋
- Java的值傳遞和引用傳遞Java
- JS的方法引數傳遞(按值傳遞)JS
- Java 從陣列來看值傳遞和引用傳遞Java陣列
- java值傳遞Java
- 這一次,徹底解決Java的值傳遞和引用傳遞Java
- Java只有值傳遞Java
- Java - 是值傳遞還是引用傳遞Java
- JavaScript的值傳遞和引用傳遞JavaScript
- 為什麼說Java中只有值傳遞。Java
- Java是值傳遞還是引用傳遞,又是怎麼體現的Java
- 微信小程式中的值傳遞微信小程式
- go 值傳遞和地址傳遞的例子Go
- 解惑4:java是值傳遞還是引用傳遞Java
- 方法的過載、可變形參的方法、方法的引數值傳遞機制、遞迴方法遞迴
- 面試官問:Go 中的引數傳遞是值傳遞還是引用傳遞?面試Go
- python的賦值傳遞Python賦值
- 這一次,讓你徹底理解Java的值傳遞和引用傳遞!Java
- 值傳遞和引用傳遞
- [精]--這一次,讓你徹底明白Java的值傳遞和引用傳遞!Java
- AbilitySlice之間的傳遞值
- JavaScript 是如何工作的:JavaScript 的共享傳遞和按值傳遞JavaScript
- PHP7中session的值跨頁傳遞失敗的解決辦法PHPSession
- ABAP 方法呼叫的引數傳遞裡,透過引用傳遞的方式,能修改原始引數值嗎?
- 怎樣向自定義標籤裡傳遞用Controller裡的assign()傳遞到頁面上的值Controller
- html、php和js值的傳遞(使用ajax進行傳遞)HTMLPHPJS
- Vue父子之間的值傳遞Vue
- 按值傳遞
- 在Java中,HashMap中是用哪些方法來解決雜湊衝突的?JavaHashMap
- 快速搞懂值傳遞與引用傳遞
- java中傳值方式的個人理解Java
- Python的函式引數傳遞:傳值?引用?Python函式
- Day30--值傳遞和引用傳遞
- Java入門第12天 (方法過載 ,方法的引數傳遞)Java
- python傳遞實參的方法Python
- Java中將方法作為引數傳遞5種方式Java
- 面試官:兄弟,說說Java到底是值傳遞還是引用傳遞面試Java
- 從request中傳遞過來的引數資訊