<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    thinking

    one platform thousands thinking

    語言深入:java中究竟是傳值還是傳引用

    首先,推薦對(duì)Java有一定理解的同仁一本書《Practical Java》。在《Practical Java》中也有一個(gè)章節(jié)介紹Java中關(guān)于傳值和傳引用的問題,堪稱經(jīng)典。《Practical Java》在Java中,事實(shí)上底層工作原理不存在傳引用的概念,這也象《Practical Java》中所說的那樣,Java中只有傳值。這句話理解起來需要費(fèi)一定的周折。

          熟悉C的程序員都用過指針,對(duì)指針可謂愛之深恨之切。指針是指向一塊內(nèi)存地址的內(nèi)存數(shù)據(jù)(有些拗口),也就是說指針本身是一個(gè)占用4字節(jié)內(nèi)存的int(32 位系統(tǒng)內(nèi)),而這個(gè)int值恰恰又是另一塊內(nèi)存的地址。比如"hello"這個(gè)字串,存放在@0x0000F000這個(gè)地址到@0x0000F005這段內(nèi)存區(qū)域內(nèi)(包括0x00的結(jié)束字節(jié))。而在@0x0000FFF0到@0x0000FFF03這四個(gè)字節(jié)內(nèi)存放著一個(gè)int,這個(gè)int的值是 @0x0000F000。這樣就形成了一個(gè)指向"hello"字串的指針。

          在Java中,很多人說沒有指針,事實(shí)上,在Java更深層次里,到處都是大師封裝好的精美絕倫的指針。為了更容易的講解Java中關(guān)于類和類型的調(diào)用,Java中出現(xiàn)了值與引用的說法。淺顯的來說,我們可以認(rèn)為Java中的引用與C中的指針等效(其實(shí)差別非常非常大,但是為了說明我們今天的問題,把他們理解為等效是沒有任何問題的)。

          所謂傳引用的說法是為了更好的講解調(diào)用方式。基于上面對(duì)指針的理解,我們不難看出,指針其實(shí)也是一個(gè)int值,所謂傳引用,我們是復(fù)制了復(fù)制了指針的int值進(jìn)行傳遞。為了便于理解,我們可以姑且把指針看作一種數(shù)據(jù)類型,透明化指針的int特性,從而提出傳引用的概念。

          重申一遍:Java中只有傳值。

          1所謂傳值和傳引用

          傳值和傳引用的問題一直是Java里爭論的話題。與C++不同的,Java里面沒有指針的概念,Java的設(shè)計(jì)者巧妙的對(duì)指針的操作進(jìn)行了管理。事實(shí)上,在懂C++的Java程序員眼中,Java到處都是精美絕倫的指針。
    下面舉個(gè)簡單的例子,說明什么是傳值,什么是傳引用。
    //例1
    void method1(){
    int x=0;
    this.change(x);
    System.out.println(x);
    }

    void change(int i){
    i=1;
    }

          很顯然的,在mothod1中執(zhí)行了change(x)后,x的值并不會(huì)因?yàn)閏hange方法中將輸入?yún)?shù)賦值為1而變成1,也就是說在執(zhí)行change(x)后,x的值z依然是0。這是因?yàn)閤傳遞給change(int i)的是值。這就是最簡單的傳值。
    同樣的,進(jìn)行一點(diǎn)簡單的變化。
    //例2
    void method1(){
    StringBuffer x=new StringBuffer("Hello");
    this.change(x);
    System.out.println(x);
    }

    void change(StringBuffer i){
    i.append(" world!");
    }
          看起來沒什么變化,但是這次mothed1中執(zhí)行了change (x)后,x的值不再是"Hello"了,而是變成了"Hello world!"。這是因?yàn)閤傳遞給change(i)的是x的引用。這是最經(jīng)典的傳引用。
    似乎有些奇怪了,兩段程序沒有特別的不同,可是為什么一個(gè)傳的是值而另一個(gè)傳的是引用呢?......

          2非要搞清楚傳值還是傳引用的問題嗎?

          搞清楚這自然是有必要的,不然我也不需要寫這么多了,不過的確沒有到"非要"的地步。


          首先,如果我們不太關(guān)心什么是傳值什么是傳引用,我們一樣能寫出漂亮的代碼,但是這些代碼在運(yùn)行過程中可能會(huì)存在著極大的隱患。全局變量是讓大家深惡痛絕(又難以割舍)的東西,原因就是使用全局變量要特別注意數(shù)據(jù)的保護(hù)。如果在多線程的程序里使用全局變量簡直就等于跟自己過不去。不了解傳值和傳引用的問題,跟使用全局變量不考慮數(shù)據(jù)保護(hù)的罪過是不相上下下的,甚至有時(shí)候比它還要糟。你會(huì)莫名其妙,為什么我的返回參數(shù)沒有起作用,為什么我傳進(jìn)去的參數(shù)變成了這樣......?
    一個(gè)例子:
    //例3
    void mothed1(){
    int x=0;
    int y=1;
    switchValue(x,y);
    System.out.println("x="+x);
    System.out.println("y="+y);
    }
    void switchValue(int a,int b){

    int c=a;
    a=b;
    b=c;
    }
          上面是一個(gè)交換a,b值的函數(shù),看起來似乎蠻正確的,但是這個(gè)函數(shù)永遠(yuǎn)也不會(huì)完成你想要的工作。
          還有一個(gè)例子:
    //例4
    StringBuffer a=new StringBuffer("I am a ");
    StringBuffer b=a;
    a.append("after append");
    a=b;
    System.out.println("a="+a);
    在編程過程中,經(jīng)常會(huì)遇到這種情況,一個(gè)變量的值要被臨時(shí)改變一下,等用完之后再恢復(fù)到開始的值。就好像上面的例子,a為了保持它的值,使用b=a做賦值,之后a被改變,再之后a把暫存在b里面的值取回來。這是我們一廂情愿的想法,而事實(shí)上,這段代碼執(zhí)行后,你會(huì)發(fā)現(xiàn)a的值已經(jīng)改變了。
    以上是兩個(gè)最簡單的例子,真正的程序開發(fā)過程中,比這要復(fù)雜的情況每天都會(huì)遇到。

          3類型和類

          Java 提出的思想,在Java里面任何東西都是類。但是Java里面同時(shí)還有簡單數(shù)據(jù)類型int,byte,char,boolean,與這些數(shù)據(jù)類型相對(duì)應(yīng)的類是Integer,Byte,Character,Boolean,這樣做依然不會(huì)破壞Java關(guān)于任何東西都是類的提法。這里提到數(shù)據(jù)類型和類似乎和我們要說的傳值和傳引用的問題無關(guān),但這是我們分辨?zhèn)髦岛蛡饕玫幕A(chǔ)。

          4試圖分辨?zhèn)髦颠€是傳引用

          為什么是"試圖分辨"呢?很簡單,傳值和傳引用的問題無處不在,但是似乎還沒有人能正統(tǒng)的給出標(biāo)準(zhǔn),怎樣的就是值拷貝調(diào)用,怎樣的就是引用調(diào)用。面對(duì)這個(gè)問題,我們更多的應(yīng)該是來自平時(shí)積累對(duì)Java的理解。
          回過頭來,我們分析一下上面的幾個(gè)例子:
          先看例1,即使你不明白為什么,但是你應(yīng)該知道這樣做肯定不會(huì)改變x的值。為了方便說明,我們給例子都加上行號(hào)。
    //例1
    1 void method1(){
    2   int x=0;
    3   this.change(x);
    4 }
    5
    6 void change(int i){
    7 i=7;
    8}
          讓我們從內(nèi)存的存儲(chǔ)方式看一下x和I之間到底是什么關(guān)系。
          在執(zhí)行到第2行的時(shí)候,變量x指向一個(gè)存放著int 0的內(nèi)存地址。

          變量x---->[存放值0]

          執(zhí)行第3行調(diào)用change(x)方法的時(shí)候,內(nèi)存中是這樣的情形:x把自己值在內(nèi)存中復(fù)制一份,然后變量i指向這個(gè)被復(fù)制出來的0。

          變量x---->[存放值0]
                            ↓進(jìn)行了一次值復(fù)制
          變量x---->[存放值0]

          這時(shí)候再執(zhí)行到第7行的時(shí)候,變量i的被賦值為7,而這一步的操作已經(jīng)跟x沒有任何關(guān)系了。

          變量x---->[存放值0]
                 
          變量x---->[存放值7]

          說到這里應(yīng)該已經(jīng)理解為什么change(x)不能改變x的值了吧?因?yàn)檫@個(gè)例子是傳值的。
    那么,試著分析一下為什么例三中的switchValue()方法不能完成變量值交換的工作?
    再看例2。
    //例2
    1void method1(){
    2 StringBuffer x=new StringBuffer("Hello");
    3 this.change(x);
    4}
    5
    6void change(StringBuffer i){
    7 i.append(" world!");
    8}
          例2似乎和例1從代碼上看不出什么差別,但是執(zhí)行結(jié)果卻是change(x)能改變x的值。依然才從內(nèi)存的存儲(chǔ)角度來看看例2的蹊蹺在哪里。
    在執(zhí)行到第2行時(shí)候,同例1一樣,x指向一個(gè)存放"Hello"的內(nèi)存空間。

          變量x---->[存放值"Hello"]

          接下來執(zhí)行第三行change(x),注意,這里就與例1有了本質(zhì)的不同:調(diào)用change(x)時(shí),變量i也指向了x指向的內(nèi)存空間,而不是指向x的一個(gè)拷貝。

          變量x "
                  -->[存放值"Hello"]
          變量x /

          于是,第7行對(duì)i調(diào)用append方法,改變i指向的內(nèi)存空間的值,x的值也就隨之改變了。

          變量x "
                  -->[追加為"Hello World!"]
          變量x /

          為什么x值能改變呢?因?yàn)檫@個(gè)例子是傳引用的。
    這幾個(gè)例子是明白了,可是很多人會(huì)開始有另一個(gè)疑問了:這樣看來,到底什么時(shí)候是傳的值什么時(shí)候是傳得引用呢?于是,我們前面講到的類型和類在這里就派上了用場對(duì)于參數(shù)傳遞,如果是簡單數(shù)據(jù)類型,那么它傳遞的是值拷貝,對(duì)于類的實(shí)例它傳遞的是類的引用。需要注意的是,這條規(guī)則只適用于參數(shù)傳遞。為什么這么說呢?我們看看這樣一個(gè)例子:
    //例5
    String str="abcdefghijk";
    str.replaceAll("b","B");
          這兩句執(zhí)行后,str的內(nèi)容依然是"abcdefghijk",但是我們明明是對(duì)str操作的,為什么是這樣的呢?因?yàn)閟tr的值究竟會(huì)不會(huì)被改變完全取決于replaceAll這個(gè)方法是怎么實(shí)現(xiàn)的。類似的,有這樣一個(gè)例子:
    //例6
    1 void method1() {
    2 StringBuffer x = new StringBuffer("Hello");
    3 change1(x);
    4 System.out.println(x);
    5 }
    6
    7 void method2() {
    8 StringBuffer x = new StringBuffer("Hello");
    9 change2(x);
    10 System.out.println(x);
    11 }
    12
    13 void change1(StringBuffer sb) {
    14 sb.append(" world!");
    15 }
    16
    17 void change2(StringBuffer sb) {
    18 sb = new StringBuffer("hi");
    19 sb.append(" world!");
    20 }
          調(diào)用method1(),屏幕打印結(jié)果為:"Hello world!"
    調(diào)用method2(),我們認(rèn)為結(jié)果應(yīng)該是"hi world",因?yàn)閟b傳進(jìn)來的是引用。可是實(shí)際執(zhí)行的結(jié)果是"Hello"!
    難道change2()又變成傳值了?!其實(shí)change1()和change2()的確都是通過參數(shù)傳入引用,但是在方法內(nèi)部因?yàn)樘幚矸椒ǖ牟煌菇Y(jié)果大相徑庭。我們還是從內(nèi)存的角度分析:
    執(zhí)行method1()和change1()不用再多說了,上面的例子已經(jīng)講解過,這里我們分析一下method2()和change2()。
          程序執(zhí)行到第8行,x指向一個(gè)存放著"Hello"的內(nèi)存空間。

          變量x---->[存放值"Hello"]

          第9行調(diào)用change2,將sb指向x指向的內(nèi)存空間,也就是傳入x的引用。

          變量x "
                  -->[存放值"Hello"]
          變量x /

          到這里為止還沒有什么異樣,接下來執(zhí)行18行,這里就出現(xiàn)了類似傳入值拷貝的變化:new 方法并沒有改變sb指向內(nèi)存的內(nèi)容,而是在內(nèi)從中開辟了一塊新的空間存放串"hi",同時(shí)sb指向了這塊空間。

          變量x---->[存放值"Hello"]
                ×原有的引用被切斷
          變量x---->[另一塊存放"hi"的空間]

          接下來再對(duì)sb進(jìn)行append已經(jīng)和x沒有任何關(guān)系了。
    所以,還有一條不成規(guī)則的規(guī)則:對(duì)于函數(shù)調(diào)用,最終效果是什么完全看函數(shù)內(nèi)部的實(shí)現(xiàn)。比較標(biāo)準(zhǔn)的做法是如果會(huì)改變引用的內(nèi)容,則使用void作為方法返回值,而不會(huì)改變引用內(nèi)容的則在返回值中返回新的值。
    雖然已經(jīng)說了這么多,但是感覺傳值還是傳引用的問題依然沒有完全說清楚。因?yàn)檫@個(gè)問題本身就是很難歸納總結(jié)的問題,所以更多的理解要靠平時(shí)的積累和形成。下面幾個(gè)例子,給大家嘗試進(jìn)行分析。
    //例7,打印結(jié)果是什么?
    public static void main(String[] args) {
    int a;
    int b;
    StringBuffer c;
    StringBuffer d;
    a = 0;
    b = a;
    c = new StringBuffer("This is c");
    d = c;

    a = 2;
    c.append("!!");

    System.out.println("a=" + a);
    System.out.println("b=" + b);
    System.out.println("c=" + c);
    System.out.println("d=" + d);
    }

    //例8,打印結(jié)果是什么?
    public class Test{

            public static void main(String[] args) {

             StringBuffer sb = new StringBuffer("Hello ");

             System.out.println("Before change, sb = " + sb);

             changeData(sb);

             System.out.println("After changeData(n), sb = " + sb);

         }

         

            public static void changeData(StringBuffer strBuf) {

                StringBuffer sb2 = new StringBuffer("Hi ");

                strBuf = sb2;

                sb2.append("World!");

         }

    }

    如果本文對(duì)您有幫助并且要鼓勵(lì)我的話,請(qǐng)掃描如下二維碼支持本人的勞動(dòng)成果,多謝了!


    posted on 2009-03-24 18:21 lau 閱讀(1815) 評(píng)論(5)  編輯  收藏 所屬分類: J2SE

    Feedback

    # re: 語言深入:java中究竟是傳值還是傳引用 2009-03-24 19:31 witbrave

    “×原有的引用被切斷”,原來是這么回事,這個(gè)應(yīng)該標(biāo)紅色。這個(gè)用C++/C中指針的用法也是一樣的作用吧。看你研究得那么深,請(qǐng)問一下,參數(shù)傳遞時(shí)的“拷貝”(一種低效率的做法)在Java中是否存在,何時(shí)發(fā)生?  回復(fù)  更多評(píng)論   

    # re: 語言深入:java中究竟是傳值還是傳引用 2009-03-26 15:57 lau

    對(duì)基本數(shù)據(jù)類型賦值存儲(chǔ)了實(shí)際的值,而并非指向一個(gè)對(duì)象。
    參數(shù)拷貝是不存在的,應(yīng)該說成是值拷貝  回復(fù)  更多評(píng)論   

    # re: 語言深入:java中究竟是傳值還是傳引用 2010-04-20 11:16 lau

    基本類型的表面特征就是第一個(gè)字母小寫。
    8種byte char short int long float double boolean  回復(fù)  更多評(píng)論   

    # re: 語言深入:java中究竟是傳值還是傳引用 2010-04-20 11:32 lau

    String就相當(dāng)于是char[]的包裝類。包裝類的特質(zhì)之一就是在對(duì)其值進(jìn)行操作時(shí)會(huì)體現(xiàn)出其對(duì)應(yīng)的基本類型的性質(zhì)。在參數(shù)傳遞時(shí)對(duì)這些包裝類的值操作實(shí)際上都是通過對(duì)其對(duì)應(yīng)的基本類型操作而實(shí)現(xiàn)的。在參數(shù)傳遞時(shí),包裝類采用的是傳值的方式,而不是傳地址的方式,所以函數(shù)中的值改變并不會(huì)影響原來的值。  回復(fù)  更多評(píng)論   

    # re: 語言深入:java中究竟是傳值還是傳引用 2011-03-07 19:02 lau

    這種講法不夠科學(xué),在Nicholas的javascript for web developers中講得比較科學(xué)  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 无码av免费网站| 女人18毛片水真多免费看| 亚洲网址在线观看| 免费看大美女大黄大色| 黄视频在线观看免费| 亚洲AV成人无码天堂| 国产成人免费片在线观看| 99re6在线视频精品免费| 亚洲制服丝袜中文字幕| 亚洲综合国产精品第一页| 久热中文字幕在线精品免费| 免费一级做a爰片久久毛片潮| 亚洲资源在线观看| 免费欧洲美女牲交视频| 在线人成精品免费视频| 免费看一级高潮毛片| 亚洲国产模特在线播放| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 成人免费无码视频在线网站| 一个人看的免费视频www在线高清动漫 | 免费阿v网站在线观看g| 久久嫩草影院免费看夜色| 亚洲人成在线播放| 国产亚洲精品看片在线观看| 我想看一级毛片免费的| 免费成人在线电影| 日本精品久久久久久久久免费| 亚洲a级成人片在线观看| 亚洲精品无码久久一线| 国产一区二区三区在线免费| 在线看免费观看AV深夜影院| 最近国语视频在线观看免费播放| 亚洲日本成本人观看| 亚洲影院在线观看| 亚洲国产另类久久久精品黑人| 国产又粗又长又硬免费视频| 可以免费看黄视频的网站| 无码国产精品一区二区免费模式| 九九99热免费最新版| 老湿机一区午夜精品免费福利| 中文有码亚洲制服av片|