深入解析java中的值傳遞和引用傳遞
?????辟謠時(shí)間
錯(cuò)誤理解一:值傳遞和引用傳遞,區(qū)分的條件是傳遞的內(nèi)容,如果是個(gè)值,就是值傳遞。如果是個(gè)引用,就是引用傳遞。
錯(cuò)誤理解二:Java是引用傳遞。
錯(cuò)誤理解三:傳遞的參數(shù)如果是普通類型,那就是值傳遞,如果是對(duì)象,那就是引用傳遞。
實(shí)參與形參
在Java中定義方法的時(shí)候是可以定義參數(shù)的。
如Java中main方法,public static void main(String[] args)
,這里面args就是參數(shù)。參數(shù)在程序語(yǔ)言中分形式參數(shù)和實(shí)際參數(shù)。
形式參數(shù):是在定義函數(shù)名和函數(shù)體的時(shí)候使用的參數(shù),目的是用來(lái)接收調(diào)用該函數(shù)時(shí)傳入的參數(shù)。
實(shí)際參數(shù):在調(diào)用有參函數(shù)時(shí),主調(diào)函數(shù)和被調(diào)函數(shù)之間有數(shù)據(jù)傳遞關(guān)系。在主調(diào)函數(shù)中調(diào)用一個(gè)函數(shù)時(shí),函數(shù)名后面括號(hào)中的參數(shù)稱為“實(shí)際參數(shù)”。
代碼示例:
public static void main(String[] args) { ParamTest pt = new ParamTest(); pt.sout("Hollis"); // 實(shí)際參數(shù):Hollis } public void sout(String name) { // 形式參數(shù):name System.out.println(name); }
實(shí)際參數(shù)是調(diào)用有參方法時(shí)真正傳遞的內(nèi)容,而形式參數(shù)是用于接收實(shí)參內(nèi)容的參數(shù)。
求值策略
我們說(shuō)當(dāng)進(jìn)行方法調(diào)用時(shí),需要把實(shí)際參數(shù)傳遞給形式參數(shù),那么傳遞的過(guò)程中到底傳遞的是什么東西呢?
這其實(shí)是程序設(shè)計(jì)中求值策略(Evaluation strategies)的概念。
在計(jì)算機(jī)科學(xué)中,求值策略是確定編程語(yǔ)言中表達(dá)式的求值的一組(通常確定性的)規(guī)則。求值策略定義何時(shí)和以何種順序求值給函數(shù)的實(shí)際參數(shù)、什么時(shí)候把它們代換入函數(shù)、和代換以何種形式發(fā)生。
求值策略分為兩大基本類,基于如何處理給函數(shù)的實(shí)際參數(shù),分位嚴(yán)格的和非嚴(yán)格的。
??????嚴(yán)格求值
在“嚴(yán)格求值”中,函數(shù)調(diào)用過(guò)程中,給函數(shù)的實(shí)際參數(shù)總是在應(yīng)用這個(gè)函數(shù)之前求值。多數(shù)現(xiàn)存編程語(yǔ)言對(duì)函數(shù)都用嚴(yán)格求值。所以本文只關(guān)注嚴(yán)格求值。
嚴(yán)格求值中有三個(gè)關(guān)鍵求值策略是比較關(guān)心的——
傳值調(diào)用(Call by value)、傳引用調(diào)用(Call by reference)、傳共享對(duì)象調(diào)用(Call by sharing)。
- 傳值調(diào)用(值傳遞)
- 傳值調(diào)用中,實(shí)際參數(shù)先被求值,然后其值通過(guò)復(fù)制,被傳遞給被調(diào)函數(shù)的形式參數(shù)。因?yàn)樾问絽?shù)拿到的只是一個(gè)"局部拷貝",所以如果在被調(diào)函數(shù)中改變了形式參數(shù)值,不會(huì)改變實(shí)際參數(shù)值。
- 傳引用調(diào)用(引用傳遞)
- 傳引用調(diào)用中,傳遞給函數(shù)的是它的實(shí)際參數(shù)的隱式引用而不是實(shí)參的拷貝。因?yàn)閭鬟f的是引用,所以,如果在被調(diào)函數(shù)中改變形式參數(shù)值,改變對(duì)調(diào)用者來(lái)說(shuō)是可見(jiàn)的。
- 傳共享對(duì)象調(diào)用(共享對(duì)象傳遞)
- 傳共享對(duì)象調(diào)用中,先獲取到實(shí)際參數(shù)的地址,然后將其復(fù)制,并把該地址的拷貝傳遞給被調(diào)函數(shù)的形式參數(shù)。因?yàn)閰?shù)的地址都指向同一個(gè)對(duì)象,所以我們稱也之為"傳共享對(duì)象",所以,如果在被調(diào)函數(shù)中改變形式參數(shù)值,調(diào)用者是可以看到這種變化的。
不知道發(fā)現(xiàn)沒(méi)有,其實(shí)傳共享對(duì)象調(diào)用和傳值調(diào)用的過(guò)程幾乎是一樣的,都是進(jìn)行"求值"、"拷貝"、"傳遞"。你再品,你再細(xì)品。
但是,傳共享對(duì)象調(diào)用和內(nèi)傳引用調(diào)用的結(jié)果又是一樣的,都是在被調(diào)函數(shù)中如果改變參數(shù)的內(nèi)容,那么這種改變也會(huì)對(duì)調(diào)用者有影響。你再品,你再細(xì)品。
那么,共享對(duì)象傳遞和值傳遞以及引用傳遞之間到底有很么關(guān)系呢?
對(duì)于這個(gè)問(wèn)題,我們應(yīng)該關(guān)注過(guò)程,而不是結(jié)果,因?yàn)閭鞴蚕韺?duì)象調(diào)用的過(guò)程和傳值調(diào)用的過(guò)程是一樣的,且都有一步關(guān)鍵的操作,那就是"復(fù)制",所以,通常認(rèn)為傳共享對(duì)象調(diào)用是傳值調(diào)用的特例。
先把傳共享對(duì)象調(diào)用放在一邊,再來(lái)回顧下傳值調(diào)用和傳引用調(diào)用的主要區(qū)別:
傳值調(diào)用:在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中。
傳引用調(diào)用:在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)的引用直接傳遞到函數(shù)中。
????所以,兩者主要區(qū)別就是是直接傳遞的,還是傳遞的是一個(gè)副本。
這里舉一個(gè)形象的例子。來(lái)深入理解一下傳值調(diào)用和傳引用調(diào)用:
你有一把鑰匙,當(dāng)你的朋友想要去你家的時(shí)候,如果你
直接
的鑰匙給他,這就是引用傳遞。這種情況下,如果他對(duì)這把鑰匙做了什么事情,比如他在鑰匙上刻下了自己名字,那么這把鑰匙還給你的時(shí)候,你自己的鑰匙上也會(huì)多出他刻的名字。你有一把鑰匙,當(dāng)你的朋友想要去你家的時(shí)候,你
復(fù)刻
了一把新鑰匙給他,自己的還在自己手里,這就是值傳遞。這種情況下,他對(duì)這把鑰匙做什么都不會(huì)影響你手里的這把鑰匙。
為什么說(shuō)Java中只有值傳遞
Java的求值策略
前面介紹過(guò)了傳值調(diào)用、傳引用調(diào)用以及傳值調(diào)用的特例傳共享對(duì)象調(diào)用,那么,Java中是采用的哪種求值策略呢?
很多人說(shuō)Java中的基本數(shù)據(jù)類型是值傳遞的,這個(gè)基本沒(méi)有什么可以討論的,普遍都是這樣認(rèn)為的。
但有很多人卻誤認(rèn)為Java中的對(duì)象傳遞是引用傳遞。之所以會(huì)有這個(gè)誤區(qū),主要是因?yàn)镴ava中的變量和對(duì)象之間是有引用關(guān)系的。Java語(yǔ)言中是通過(guò)對(duì)象的引用來(lái)操縱對(duì)象的。所以,很多人會(huì)認(rèn)為對(duì)象的傳遞是引用的傳遞。
且很多人還舉出以下的代碼示例:
public static void main(String[] args) { Test pt = new Test(); User hollis = new User(); hollis.setName("Hollis"); hollis.setGender("Male"); pt.pass(hollis); System.out.println("print in main , user is " + hollis); } public void pass(User user) { user.setName("hollischuang"); System.out.println("print in pass , user is " + user); }
輸出結(jié)果:
print in pass , user is User{name='hollischuang', gender='Male'} print in main , user is User{name='hollischuang', gender='Male'}
可以看到對(duì)象類型在被傳遞到pass方法后,在方法內(nèi)改變其內(nèi)容,最終調(diào)用方main方法中的對(duì)象也變了。
所以很多人說(shuō),這和引用傳遞的現(xiàn)象是一樣,就是在方法內(nèi)改變參數(shù)的值,會(huì)影響到調(diào)用方。
但是,其實(shí)這是走進(jìn)了一個(gè)誤區(qū)。
Java中的對(duì)象傳遞
很多人通過(guò)代碼示例的現(xiàn)象說(shuō)明Java對(duì)象是引用傳遞,那么就從現(xiàn)象入手,先來(lái)反駁下這個(gè)觀點(diǎn)。
前面說(shuō)過(guò),無(wú)論是值傳遞,還是引用傳遞,只不過(guò)是求值策略的一種,那求值策略還有很多,如前面提到的共享對(duì)象傳遞的現(xiàn)象和引用傳遞也是一樣的。那憑什么就說(shuō)Java中的參數(shù)傳遞就一定是引用傳遞而不是共享對(duì)象傳遞呢?
那么,Java中的對(duì)象傳遞,到底是哪種形式呢?其實(shí),就是共享對(duì)象傳遞。
在 《The Java™ Tutorials》中,是有關(guān)于這部分內(nèi)容的說(shuō)明的。
1、首先是關(guān)于基本類型描述如下:
Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.
即,原始參數(shù)通過(guò)值傳遞給方法。這意味著對(duì)參數(shù)值的任何更改都只存在于方法的范圍內(nèi)。當(dāng)方法返回時(shí),參數(shù)將消失,對(duì)它們的任何更改都將丟失。
2、關(guān)于對(duì)象傳遞的描述如下:
Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.
即,引用數(shù)據(jù)類型參數(shù)(如對(duì)象)也按值傳遞給方法。這意味著,當(dāng)方法返回時(shí),傳入的引用仍然引用與以前相同的對(duì)象。但是,如果對(duì)象字段具有適當(dāng)?shù)脑L問(wèn)級(jí)別,則可以在方法中更改這些字段的值。
這一點(diǎn)官方文檔已明確指出,Java就是值傳遞,只不過(guò)是把對(duì)象的引用當(dāng)做值傳遞給方法。你細(xì)品,這不就是共享對(duì)象傳遞么?
其實(shí)Java中使用的求值策略就是傳共享對(duì)象調(diào)用,也就是說(shuō),Java會(huì)將對(duì)象的地址的拷貝傳遞給被調(diào)函數(shù)的形式參數(shù)。只不過(guò)"傳共享對(duì)象調(diào)用"這個(gè)詞并不常用,所以Java社區(qū)的人通常說(shuō)"Java是傳值調(diào)用",這么說(shuō)也沒(méi)錯(cuò),因?yàn)閭鞴蚕韺?duì)象調(diào)用其實(shí)是傳值調(diào)用的一個(gè)特例。
值傳遞和共享對(duì)象傳遞的現(xiàn)象沖突嗎?
看到這里可能會(huì)有一個(gè)疑問(wèn),既然共享對(duì)象傳遞是值傳遞的一個(gè)特例,那么為什么現(xiàn)象是完全不同的?
難道值傳遞過(guò)程中,如果在被調(diào)方法中改變了值,也有可能會(huì)對(duì)調(diào)用者有影響嗎?那到底什么時(shí)候會(huì)影響什么時(shí)候不會(huì)影響呢?
其實(shí)是不沖突的,之所以會(huì)有這種疑惑,是因?yàn)榇蠹覍?duì)于到底是什么是"改變值"有誤解。
我們先回到上面的例子中來(lái),看一下調(diào)用過(guò)程中實(shí)際上發(fā)生了什么?
在參數(shù)傳遞過(guò)程中,實(shí)際參數(shù)的地址0X1213456
被拷貝給了形參。這個(gè)過(guò)程其實(shí)就是值傳遞,只不過(guò)傳遞的值得內(nèi)容是對(duì)象的應(yīng)用。
那為什么改了user中的屬性的值,卻對(duì)原來(lái)的user產(chǎn)生了影響呢?
其實(shí),這個(gè)過(guò)程就好像是:你復(fù)制了一把你家里的鑰匙給到你的朋友,他拿到鑰匙以后,并沒(méi)有在這把鑰匙上做任何改動(dòng),而是通過(guò)鑰匙打開(kāi)了你家里的房門(mén),進(jìn)到屋里,把你家的電視給砸了。
這個(gè)過(guò)程,對(duì)你手里的鑰匙來(lái)說(shuō),是沒(méi)有影響的,但是你的鑰匙對(duì)應(yīng)的房子里面的內(nèi)容卻是被人改動(dòng)了。
也就是說(shuō),Java對(duì)象的傳遞,是通過(guò)復(fù)制的方式把引用關(guān)系傳遞了,如果我們沒(méi)有改引用關(guān)系,而是找到引用的地址,把里面的內(nèi)容改了,是會(huì)對(duì)調(diào)用方有影響的,因?yàn)榇蠹抑赶虻氖峭粋€(gè)共享對(duì)象。
那么,如果我們改動(dòng)一下pass方法的內(nèi)容:
public void pass(User user) { user = new User(); user.setName("hollischuang"); user.setGender("Male"); System.out.println("print in pass , user is " + user); }
上面代碼中,在pass方法中,重新new一個(gè)user對(duì)象,并改變了他的值,輸出結(jié)果如下:
print in pass , user is User{name='hollischuang', gender='Male'} print in main , user is User{name='Hollis', gender='Male'}
再看一下整個(gè)過(guò)程中發(fā)生了什么:
這個(gè)過(guò)程,就像你復(fù)制了一把鑰匙給到你的朋友,你的朋友拿到你給他的鑰匙之后,找個(gè)鎖匠把鑰匙修改了,他手里的那把鑰匙變成了開(kāi)他家鎖的鑰匙。這時(shí)候,他打開(kāi)自己家,就算是把房子點(diǎn)了,對(duì)你手里的鑰匙,和你家的房子都沒(méi)有影響。
所以,Java中的對(duì)象傳遞,如果是修改引用,是不會(huì)對(duì)原來(lái)的對(duì)象有任何影響的,但是如果直接修改共享對(duì)象的屬性的值,是會(huì)對(duì)原來(lái)的對(duì)象有影響的。
總結(jié)
我們知道,編程語(yǔ)言中需要進(jìn)行方法間的參數(shù)傳遞,這個(gè)傳遞的策略叫做求值策略。
在程序設(shè)計(jì)中,求值策略有很多種,比較常見(jiàn)的就是值傳遞和引用傳遞。還有一種值傳遞的特例——共享對(duì)象傳遞。
值傳遞和引用傳遞最大的區(qū)別是傳遞的過(guò)程中有沒(méi)有復(fù)制出一個(gè)副本來(lái),如果是傳遞副本,那就是值傳遞,否則就是引用傳遞。
在Java中,其實(shí)是通過(guò)值傳遞實(shí)現(xiàn)的參數(shù)傳遞,只不過(guò)對(duì)于Java對(duì)象的傳遞,傳遞的內(nèi)容是對(duì)象的引用。
可以總結(jié)說(shuō),Java中的求值策略是共享對(duì)象傳遞,這是完全正確的。
但是,為了讓大家都能理解,說(shuō)Java中只有值傳遞,只不過(guò)傳遞的內(nèi)容是對(duì)象的引用。
但是,絕對(duì)不能認(rèn)為Java中有引用傳遞。
到此這篇關(guān)于深入解析java中的值傳遞和引用傳遞的文章就介紹到這了,更多相關(guān)java中的值傳遞和引用傳遞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多線程Callable接口實(shí)現(xiàn)代碼示例
相信大家對(duì)Java編程中如何創(chuàng)建線程已經(jīng)不陌生了,這篇文章就向朋友們介紹實(shí)現(xiàn)callable接口,具體實(shí)例詳見(jiàn)正文。2017-10-10

SpringBoot中整合Minio文件存儲(chǔ)的安裝部署過(guò)程

SpringBoot配置mybatis駝峰命名規(guī)則自動(dòng)轉(zhuǎn)換的實(shí)現(xiàn)

Java中IO流 RandomAccessFile類實(shí)例詳解

java導(dǎo)出csv方法實(shí)現(xiàn)講解

Spring超詳細(xì)講解創(chuàng)建BeanDefinition流程