java的传值调用什么_Java的传值调用
(本文非引戰(zhàn)或diss,只是說出自己的理解,歡迎擺正心態(tài)觀看或探討)
引子
之所以寫這篇文章是因?yàn)榍靶┨鞂懥艘黄禞ava中真的只有值傳遞么?》探討了網(wǎng)上關(guān)于Java只有值傳遞的說法,當(dāng)時(shí)寫這篇文章的緣由是因?yàn)橹翱吹奈恼轮v解的Java只有值傳遞,講的不是讓我很明白,沒有拿出比較專業(yè)的解釋或定義,沒有說服我。而我在《Java中真的只有值傳遞么?》這篇文章中又做了一些解讀,發(fā)現(xiàn)自己也是沒有抓住重點(diǎn),這才有了今天這篇文章,對(duì)之前的這篇文章做一個(gè)補(bǔ)充。
從那篇文章后,我了解到Java的參數(shù)傳遞其實(shí)牽涉到了Java語言的設(shè)計(jì)中的參數(shù)傳遞方式,可能在語言設(shè)計(jì)之時(shí)就考慮了這個(gè)問題,所以在工作之余自己簡單的研究了一下,最終也能根據(jù)自己的理解解釋一下關(guān)于Java是值傳遞還是引用傳遞的說法。
Java 是引用傳遞還是值傳遞現(xiàn)在有以下這些說法:
1、值傳遞和引用傳遞,區(qū)分的條件是傳遞的內(nèi)容,如果是個(gè)值,就是值傳遞。如果是個(gè)引用,就是引用傳遞。
2、傳遞的參數(shù)如果是普通類型,那就是值傳遞,如果是對(duì)象,那就是引用傳遞。
3、Java中只有值傳遞。
關(guān)于這個(gè)問題應(yīng)該是分情況討論的,存在即合理,或許在不同的認(rèn)識(shí)下有不同的說法,也不能簡單的就說是值傳遞還是引用傳遞。
對(duì)或錯(cuò)都是相對(duì)的。
回顧
在談這個(gè)問題之前我們先了解下值傳遞和引用傳遞的概念及現(xiàn)象。可以簡單的通過幾個(gè)例子來講解的,大概是這樣的。
值傳遞
例子1:
public?static?void?main(String[]?args){
TestJavaParamPass()?tjpp?=?new?TestJavaParamPass();
int?num?=?10;
tjpp.change(num);
System.out.println("num?in?main():"+i);
}
public?void?change(int?param){
param?=?20;
System.out.println("param?in?change():"+param);
}
控制臺(tái)輸出:
param?in?change():20
num?in?main():10
mian()方法中的int變量num傳遞給change()方法,change()方法接收到后將值改變?yōu)?0。通過看控制臺(tái)輸出,main()方法中的num變量的值沒有改變。
結(jié)論:實(shí)參沒有被形參影響,基本類型是值傳遞。
引用傳遞
例子2:
public?static?void?main(String[]?args){
TestJavaParamPass()?tjpp?=?new?TestJavaParamPass();
User?user?=?new?User();
user.setName("Jerry");
tjpp.change(user);
System.out.println("user?in?mian():"+user);
}
public?void?change(User?param){
param.setName("Tom");
System.out.println("param?in?change():"+param);
}
控制臺(tái)輸出:
param?in?change():User(name=Tom}
user?in?mian():User(name=Tom}
main()方法中的user變量傳遞給change()方法,change()方法改變了其name屬性值。通過看控制臺(tái)輸出,main()方法中的user變量的name屬性值發(fā)生改變。
結(jié)論:形參變了實(shí)參也變了,引用類型是引用傳遞。
特殊的值傳遞
例子3:
public?static?void?main(String[]?args){
TestJavaParamPass()?tjpp?=?new?TestJavaParamPass();
String?name?=?"Jerry";
tjpp.change(name);
System.out.println("name?in?main():"+i);
}
public?void?change(String?param){
param?=?"Tom";
System.out.println("param?in?change():"+param);
}
控制臺(tái)輸出:
param?in?change():Tom
name?in?mian():Jerry
String也是引用類型的數(shù)據(jù)類型,為什么值沒改變?
因?yàn)樵赾hange()方法里param = "Tom";相當(dāng)于param = new String("Tom");就相當(dāng)于param被重新賦值指向了另外一個(gè)對(duì)象。所以,其實(shí)String類型傳的是引用,只不過被重新賦值指向了別的對(duì)象了,沒有修改原對(duì)象。即,String本質(zhì)上還是引用傳遞,表像上是值傳遞。
結(jié)論:基本類型是值傳遞,引用類型是引用傳遞,String是特殊的值傳遞。
看到這樣的結(jié)論,沒有去深究過,可能大部分程序員的認(rèn)知都是這樣的。
根據(jù)上面的例子我們先初步給值傳遞和引用傳遞下個(gè)定義,以及解釋為什么大多數(shù)程序員都將String理解為是特殊的值傳遞。
概念提取
與其叫概念提取還不如叫結(jié)論總結(jié)呢。
值傳遞:基本類型的變量在被傳遞給方法時(shí),傳遞的是該變量的值(即復(fù)制自己的值傳遞給方法)。
引用傳遞:引用類型的變量在被傳遞給方法時(shí), 傳遞的是該變量的引用(即自己所指向的內(nèi)存地址)。
為什么說String是特殊的值傳遞:是因?yàn)镾tring和基本類型從表象來說表現(xiàn)出來的結(jié)果是一樣,大概是為了便于記憶這個(gè)結(jié)果才這樣說的吧。但是要知道String也是傳遞的引用,只不過它的引用被重新賦值,指向了別的對(duì)象了,所以不會(huì)影響原值。所以String不能簡單的說是值傳遞。
而僅僅根據(jù)上面的實(shí)驗(yàn)就給值傳遞,引用傳遞下這樣的結(jié)論是不是太草率了?
解析
對(duì)于文章開始時(shí)提到的那些說法,前兩種可以這樣解釋:
大概是因?yàn)閕nt沒有因?yàn)閏hange方法而改變?cè)?#xff0c;所以就說它傳過去的是自身的值,因而叫值傳遞;User對(duì)象經(jīng)過change方法后,對(duì)象的數(shù)據(jù)變了,就認(rèn)為是因?yàn)閷?shí)參和形參指向的是同一片內(nèi)存空間,內(nèi)存空間的數(shù)據(jù)變了就都變了,傳過去的是引用所以就說對(duì)象是引用傳遞。這樣說的側(cè)重點(diǎn)是傳遞的東西。
所以,如果從傳遞的東西的角度來看這兩種說法也是沒問題的呀。
至于Java只有值傳遞的說法,我查閱了一些資料結(jié)合網(wǎng)上的文章了解到了求值策略這個(gè)名詞,這大概牽涉到了語言本身的設(shè)計(jì)。所以就從這些名詞來探究Java的方法調(diào)用時(shí)參數(shù)傳遞的奧秘。
我們先來看看這些編程語言里關(guān)于參數(shù)傳遞函數(shù)調(diào)用有關(guān)的術(shù)語。
(以下術(shù)語來自Wiki )
求值策略(Evaluation strategy)
在計(jì)算機(jī)科學(xué)中,求值策略(英語:Evaluation strategy)是確定編程語言中表達(dá)式的求值的一組(通常確定性的)規(guī)則。 重點(diǎn)典型的位于函數(shù)或算子上——求值策略定義何時(shí)和以何種次序求值給函數(shù)的實(shí)際參數(shù),什么時(shí)候把它們代換入函數(shù),和代換以何種形式發(fā)生。
求值策略:是一組求值規(guī)則,用來定義如何為函數(shù)的實(shí)際參數(shù)求值。它是用來規(guī)定程序語言在方法、函數(shù)或過程調(diào)用時(shí)的傳參策略,是在程序語言設(shè)計(jì)時(shí)就應(yīng)該考慮的問題。而下面的這幾個(gè)調(diào)用方式都屬于求值策略。
傳值調(diào)用(Call by value)
“傳值調(diào)用”求值是最常見的求值策略,C和Scheme這樣差異巨大的語言都在使用。在傳值調(diào)用中實(shí)際參數(shù)被求值,其值被綁定到函數(shù)中對(duì)應(yīng)的變量上(通常是把值復(fù)制到新內(nèi)存區(qū)域)。如果函數(shù)或過程能把值賦給它的形式參數(shù),則被賦值的只是局部拷貝——就是說,在函數(shù)返回后調(diào)用者作用域里的曾傳給函數(shù)的任何東西都不會(huì)變。
傳值調(diào)用不是一個(gè)單一的求值策略,而是指一類函數(shù)的實(shí)參在被傳給函數(shù)之前就被求值的求值策略。 盡管很多使用傳值調(diào)用的編程語言(如Common Lisp、Eiffel、Java)從左至右的求值函數(shù)的實(shí)際參數(shù),某些語言(比如OCaml)從右至左的求值函數(shù)和它們的實(shí)際參數(shù),而另一些語言(比如Scheme和C)未指定這種次序(盡管它們保證順序一致性)。
傳值調(diào)用:在傳值調(diào)用中,實(shí)際參數(shù)被求值后傳遞給被調(diào)函數(shù)。也就是說傳值調(diào)用是實(shí)參在被傳給函數(shù)之前就被求值的一種求值策略。
在Java中的體現(xiàn)
那什么叫實(shí)參在被傳給函數(shù)之前就被求值呢?求的是誰的值呢?這個(gè)值又是什么呢?是怎么求得呢?
帶著這些疑問,我們來看下面的例子。
如下,在調(diào)用change()方法時(shí)實(shí)參為i,當(dāng)程序執(zhí)行到change(i)這一行時(shí),i是實(shí)參,這時(shí)i就要被求值了,會(huì)求出i的值即4傳給change()方法;change()的形參a拿到的是實(shí)參i的值,是一個(gè)拷貝副本。
偽代碼:
void?change(int?a){//拿到求得的實(shí)參的值
a?=?a/2;
}
int?i?=?4;
change(i);
System.out.print(i);
因?yàn)槭侵档母北?#xff0c;所以在函數(shù)內(nèi)對(duì)形參操作不會(huì)影響實(shí)參,所以輸出是4。
這里我們舉的例子是基本類型int類型的。那對(duì)于引用類型呢?
同樣需要對(duì)實(shí)參求值,這時(shí)得到的值是實(shí)參的地址值,形參拿到的是實(shí)參的地址值,這個(gè)地址值指向的是u1等號(hào)后面使用new關(guān)鍵字開辟出來的那片內(nèi)存空間,所以此時(shí)u2也指向這片內(nèi)存空間,所以打印出來u2將會(huì)和u1輸出同樣的內(nèi)容。
偽代碼:
void?change(User?u2){//拿到求得的實(shí)參的地址值
System.out.print(u2);
u2?=?getNewUser();
u2.setName("$%#@*")
System.out.print(u2);
}
public?static?void?main(){
User?u1?=?new?User();
u1.setName("1234");
change(u1);
System.out.print(u1);
}
然后,我們模仿上面的change(int a)的方法里,對(duì)形參接收到的值進(jìn)行改變。注意,是形參的值,對(duì)change(User u2)來說,形參u2接收到的值是地址值,我們咋改變它呢?我們可以讓u2指向另一個(gè)內(nèi)存空間,即通過getNewUser()方法獲取一個(gè)新的User對(duì)象,用這種方式給u2一個(gè)新的地址值,這不就改變了嗎。
此時(shí)我們看輸出,發(fā)現(xiàn)經(jīng)過change()方法實(shí)參u1打印信息沒變,為什么?因?yàn)閡1的地址值沒變,且u2是獲得新地址后(指向另一片內(nèi)存),在新的這片內(nèi)存里操作的,故而不會(huì)影響到之前的那片內(nèi)存空間的數(shù)據(jù)。
這樣基本類型和引用類型的實(shí)驗(yàn)方法是一樣的,看到的效果也是一樣的,即實(shí)參沒有隨形參的改變而改變。
總結(jié)
最后得出的結(jié)論:從語言設(shè)計(jì)的角度,Java的方法調(diào)用時(shí)參數(shù)的求值策略是傳值調(diào)用(Call by value)的。
如果我們想表達(dá)引用類型傳遞的是引用,僅僅是想說傳的是引用不是別的東西的話,我們可以說的明確點(diǎn):引用類型傳的是引用,和程序語言中的求值策略不沾邊 。那你說的引用傳遞就和求值策略中的傳引用調(diào)用沒關(guān)系,只是想表達(dá)傳的是引用的話也沒人會(huì)說你錯(cuò)。由此來看文章開頭提到的前2種說法是不是也有解釋的余地?
存在即合理,不同的說法有不同的前提條件不同的解釋方式。如果是從程序語言設(shè)計(jì)的求值策略角度來問Java是哪種求值策略的話,那可以肯定的說是傳值調(diào)用(Call by value)。
(以下術(shù)語摘抄自Wiki。能力有限,對(duì)這樣些專業(yè)名詞還無法完美解讀,僅供參考)
附錄
傳引用調(diào)用和傳共享對(duì)象調(diào)用都是求值策略的一種。
傳引用調(diào)用(Call by reference)
在“傳引用調(diào)用”求值中,傳遞給函數(shù)的是它的實(shí)際參數(shù)的隱式引用而不是實(shí)參的拷貝。通常函數(shù)能夠修改這些參數(shù)(比如賦值),而且改變對(duì)于調(diào)用者是可見的。因此傳引用調(diào)用提供了一種調(diào)用者和函數(shù)交換數(shù)據(jù)的方法。傳引用調(diào)用的語言中追蹤函數(shù)調(diào)用的副作用比較難,易產(chǎn)生不易察覺的bug。
很多語言支持某種形式的傳引用調(diào)用,但是很少有語言默認(rèn)使用它。FORTRAN II 是一種早期的傳引用調(diào)用語言。一些語言如C++、PHP、Visual Basic .NET、C#和REALbasic默認(rèn)使用傳值調(diào)用,但是提供一種傳引用的特別語法。
在那些使用傳值調(diào)用又不支持傳引用調(diào)用的語言里,可以用引用(引用其他對(duì)象的對(duì)象),比如指針(表示其他對(duì)象的內(nèi)存地址的對(duì)象)來模擬。C和ML就用了這種方法。這不是一種不同的求值策略(語言本身還是傳值調(diào)用)。它有時(shí)被叫做“傳地址調(diào)用”(call by address)。這可能讓人不易理解。在C之類不安全的語言里會(huì)引發(fā)解引用空指針之類的錯(cuò)誤。但ML的引用是類型安全和內(nèi)存安全的。
類似的效果可由傳共享對(duì)象調(diào)用(傳遞一個(gè)可變對(duì)象)實(shí)現(xiàn)。比如Python、Ruby。
例:C用指針模擬的傳引用調(diào)用
void?modify(int?p,?int*?q,?int*?r){
p?=?27;?//?passed?by?value:?only?the?local?parameter?is?modified
*q?=?27;?//?passed?by?value?or?reference,?check?call?site?to?determine?which
*r?=?27;?//?passed?by?value?or?reference,?check?call?site?to?determine?which
}
int?main(){
int?a?=?1;
int?b?=?1;
int?x?=?1;
int*?c?=?&x;
modify(a,?&b,?c);?//?a是傳值調(diào)用,?b通過創(chuàng)建指針實(shí)現(xiàn)引用傳遞,c是按值傳遞的指針
//b?and?x?are?changed
return?0;
}
傳共享對(duì)象調(diào)用(Call by sharing)
此方式由Barbara Liskov命名[1],并被Python、Java(對(duì)象類型)、JavaScript、Scheme、OCaml等語言使用。
與傳引用調(diào)用不同,對(duì)于調(diào)用者而言在被調(diào)用函數(shù)里修改參數(shù)是沒有影響的。如果要達(dá)成傳引用調(diào)用的效果就需要傳一個(gè)共享對(duì)象,一旦被調(diào)用者修改了對(duì)象,調(diào)用者就可以看到變化(因?yàn)閷?duì)象是共享的,沒有拷貝)。比如這段Python代碼:
def?f(l):
l.append(1)
l?=?[2]
m?=?[]
f(m)
print(m)
會(huì)輸出[1]而不是[2]。因?yàn)榱斜硎强勺兊?#xff0c;append方法改變了m。而賦值局部變量l的行為對(duì)外面作用域沒有影響(在這類語言中賦值是給變量綁定一個(gè)新對(duì)象,而不是改變對(duì)象)。
使用C/C++語言的程序員可能因不能用指針等使函數(shù)返回多個(gè)值而感到不便,但是像Python這樣的語言提供了替代方案:函數(shù)能方便的返回多個(gè)值,比C++11的std::tie更加簡單。
總結(jié)
以上是生活随笔為你收集整理的java的传值调用什么_Java的传值调用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linuxdeb安装软件命令(linux
- 下一篇: java 聚合_Java聚合