Java-String類型的參數傳遞問題

剛纔看見一個兄弟在爲Java的String傳值/傳引用問題困惑,翻箱倒櫃找到了這篇我好久之前寫的文章,發在這裏,但願能對迷惑的朋友有些幫助。 

提要:本文從實現原理的角度上闡述和剖析了:在Java語言中,以String做爲類型的變量在做爲方法參數時所表現出的「非對象」的特性。
 
1、         最開始的示例
寫代碼最重要的就是實踐,不通過反覆試驗而得出的說辭只能說是憑空遐想罷了。因此,在本文中首先以一個簡單示例來拋出核心話題:
 

 

public class StringAsParamOfMethodDemo { 

    

        public static void main(String[] args) { 

             StringAsParamOfMethodDemo StringAsParamOfMethodDemo =    

                     new StringAsParamOfMethodDemo(); 

             StringAsParamOfMethodDemo.testA(); 

        } 

    

        private void testA() { 

             String originalStr = "original"; 

             System.out.println("Test A Begin:"); 

             System.out.println("The outer String: " + originalStr); 

             simpleChangeString(originalStr); 

             System.out.println("The outer String after inner change: " + originalStr); 

             System.out.println("Test A End."); 

             System.out.println(); 

        } 

    

        public void simpleChangeString(String original) { 

             original = original + " is changed!"; 

             System.out.println("The changed inner String: " + original); 

        } 

    


 
 
這段代碼的邏輯是這樣的:先賦值一個String類型的局部變量,而後把這個變量做爲參數送進一個方法中,在這個方法中改變該變量的值。編譯運行以後,發現輸出結果是這樣的:
 
Test A Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test A End.
 
這個結果代表在方法內部對String類型的變量的從新賦值操做並無對這個變量的原型產生任何影響。好了,這個示例的邏輯和運行結果都展現清楚了,接下來咱們來對這個小程序進行分析。在這以前咱們先來回顧下Java中所謂的「傳值」和「傳引用」問題。
 
2、         Java中的「傳值」和「傳引用」問題
許多初學Java的程序員都在這個問題上有所思索,那是由於這是所謂的「C語言的傳值和傳指針問題」在Java語言上同類表現。
最後得出的結論是:
在Java中,當基本類型做爲參數傳入方法時,不管該參數在方法內怎樣被改變,外部的變量原型老是不變的,代碼相似上面的示例:
 
 

 

 

 

int number = 0; 

changeNumber(number) {number++}; //改變送進的int變量 

System.out.println(number); //這時number依然爲0
 
這就叫作「值傳遞」,即方法操做的是參數變量(也就是原型變量的一個值的拷貝)改變的也只是原型變量的一個拷貝而已,而非變量自己。因此變量原型並不會隨之改變。
                  
但當方法傳入的參數爲非基本類型時(也就是說是一個對象類型的變量),方法改變參數變量的同時變量原型也會隨之改變,代碼一樣相似上面的示例:
 
 

 

 

 

StringBuffer strBuf = new StringBuffer(「original」); 

changeStringBuffer(strBuf) {strbuf.apend(「 is changed!」)} //改變送進的StringBuffer變量 

System.out.println(strBuf); //這時strBuf的值就變爲了original is changed!    
 
這種特性就叫作「引用傳遞」,也叫作傳址,即方法操做參數變量時是拷貝了變量的引用,然後經過引用找到變量(在這裏是對象)的真正地址,並對其進行操做。當該方法結束後,方法內部的那個參數變量隨之消失。可是要知道這個變量只是對象的一個引用而已,它只是指向了對象所在的真實地址,而非對象自己,因此它的消失並不會帶來什麼負面影響。回頭來看原型變量,原型變量本質上也是那個對象的一個引用(和參數變量是同樣同樣的),當初對參數變量所指對象的改變就根本就是對原型變量所指對象的改變。因此原型變量所表明的對象就這樣被改變了,並且這種改變被保存了下來。
 
         瞭解了這個經典問題,不少細心的讀者確定會馬上提出新的疑問:「但是String類型在Java語言中屬於非基本類型啊!它在方法中的改變爲何沒有被保存下來呢!」的確,這是個問題,並且這個新疑問幾乎推翻了那個經典問題的所有結論。真是這樣麼?好,如今咱們就來繼續分析。
 
3、    關於String參數傳遞問題的曲解之一——直接賦值與對象賦值
String類型的變量做爲參數時怎麼會像基本類型變量那樣以傳值方式傳遞呢?關於這個問題,有些朋友給出過解釋,但惋惜並不正確。
一種解釋就是,對String類型的變量賦值時並無new出對象,而是直接用字符串賦值,因此Java就把這個String類型的變量看成基本類型看待了。即,應該String str = new String(「original」);,而不是String str = 「original」;。這是問題所在麼?咱們來爲先前的示例稍微改造下,運行以後看看結果就知道了。改造後的代碼以下:
 
 
        private void testB() { 

             String originalStr = new String("original"); 

             System.out.println("Test B Begin:"); 

             System.out.println("The outer String: " + originalStr); 

             changeNewString(originalStr); 

             System.out.println("The outer String after inner change: " + originalStr); 

             System.out.println("Test B End:"); 

             System.out.println(); 

             } 

    

        public void changeNewString(String original) { 

             original = new String(original + " is changed!"); 

             System.out.println("The changed inner String: " + original); 

             } 

 

 

 

 
咱們來看看此次運行結果是怎麼樣的:
 
Test B Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test B End.
 
實踐證實,這種說法是錯的。
實際上,字符串直接賦值和用new出的對象賦值的區別僅僅在於存儲方式不一樣。
簡單說明下:
字符串直接賦值時,String類型的變量所引用的值是存儲在類的常量池中的。由於」original」自己是個字符串常量,另外一方面String是個不可變類型,因此這個String類型的變量至關因而對一個常量的引用。這種狀況下,變量的內存空間大小是在編譯期就已經肯定的。
而new對象的方式是將」original」存儲到String對象的內存空間中,而這個存儲動做是在運行期進行的。在這種狀況下,Java並非把」original」這個字符串看成常量對待的,由於這時它是做爲建立String對象的參數出現的。
因此對String的賦值方式和其參數傳值問題並無直接聯繫。總之,這種解釋並非正解。
4、    關於String參數傳遞問題的曲解之二——「=」變值與方法變值
又有些朋友認爲,變值不一樣步的問題是處在改變值的方式上。
這種說法認爲:「在Java 中,改變參數的值有兩種狀況,第一種,使用賦值號「=」直接進行賦值使其改變;第二種,對於某些對象的引用,經過必定途徑對其成員數據進行改變,如經過對象的自己的方法。對於第一種狀況,其改變不會影響到被傳入該參數變量的方法之外的數據,或者直接說源數據。而第二種方法,則相反,會影響到源數據——由於引用指示的對象沒有變,對其成員數據進行改變則實質上是改變的該對象。」
這種方式聽起來彷佛有些…,咱們仍是用老辦法,編寫demo,作個小試驗,代碼以下:
 

 

        private void testC() { 

             String originalStr = new String("original"); 

             System.out.println("Test C Begin:"); 

             System.out.println("The outer String: " + originalStr); 

             changeStrWithMethod(originalStr); 

             System.out.println("The outer String after inner change: " + originalStr); 

             System.out.println("Test C End."); 

             System.out.println(); 



    

        private static void changeStrWithMethod(String original) { 

             original = original.concat(" is changed!"); 

             System.out.println("The changed inner String: " + original); 

}
 
結果以下:
 
Test C Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test C End.
 
怎麼樣,這證實了問題並非出在這,又一個解釋在實踐論據下夭折了。
那究竟是什麼緣由致使了這種情況呢?
好了,不賣關子了,下面說下個人解釋。
 
5、         String參數傳遞問題的癥結所在
其實,要想真正理解一個類或者一個API/框架的最直接的方法就是看源碼。
下面咱們來看看new出String對象的那小段代碼(String類中),也就是String類的構造函數:
 

 

 

 

        public String(String original) { 

                int size = original.count; 

                char[] originalValue = original.value; 

                char[] v; 

                if (originalValue.length > size) { 

                        // The array representing the String is bigger than the new 

                        // String itself.    Perhaps this constructor is being called 

                        // in order to trim the baggage, so make a copy of the array. 

                            int off = original.offset; 

                            v = Arrays.copyOfRange(originalValue, off, off+size); 

                } else { 

                        // The array representing the String is the same 

                        // size as the String, so no point in making a copy. 

                        v = originalValue; 

                } 

                this.offset = 0; 

                this.count = size; 

                this.value = v; 

}
 
也許你注意到了裏面的char[],這說明對String的存儲實際上經過char[]來實現的。怎麼樣?其實就是一層窗戶紙。不知道你們還記不記得在Java API中定義的那些基本類型的包裝類。好比Integer是int包裝類、Float是float的包裝類等等。對這些包裝類的值操做實際上都是經過對其對應的基本類型操做而實現的。是否是有所感悟了?對,String就至關因而char[]的包裝類。包裝類的特質之一就是在對其值進行操做時會體現出其對應的基本類型的性質。在參數傳遞時,包裝類就是如此體現的。因此,對於String在這種狀況下的展示結果的解釋就天然而然得出了。一樣的,Integer、Float等這些包裝類和String在這種狀況下的表現是相同的,具體的分析在這裏就省略了,有興趣的朋友能夠本身作作試驗。
這也就是爲何當對字符串的操做在經過不一樣方法來實現的時候,推薦你們使用StringBuffer的真正緣由了。至於StringBuffer爲何不會表現出String這種現象,你們再看看的StringBuffer的實現就會明白了,在此也再也不贅述了。
6、         寫在最後
由此String類型的參數傳遞問題的原理也就展示出來了。其實能夠看出,只要分析方式正確,思考終究得出正確結論的。
正確分析方法的基礎有二:
一、  多實踐:手千萬不要犯懶,實踐必會出真知。
二、  基於原理:搞清楚程序邏輯的最直接最簡單的方式就是看源碼,這毋庸置疑。
只要基於這兩個基礎進行分析,在不少狀況下會達到事半功倍的效果。這算是經驗之談吧,也算是分析程序的「捷徑」方式之一。

 

 

 

===============http://developer.51cto.com/art/200812/102523.htm程序員

和其它程序設計語言相似,Java語言的參數傳遞也分爲兩種:小程序

1.按值傳遞(by value)數組

適用範圍:8種基本數據類型、String對象框架

特色:在內存中複製一份數據,把複製後的數據傳遞到方法內部函數

做用:在方法內部改變參數的值,外部數據不會跟着發生改變this

2.按址傳遞(by address)spa

適用範圍:數組、除String之外的其餘全部類型的對象設計

特色:將對象的地址傳遞到方法內部指針

做用:在方法內部修改對象的內容,外部數據也會跟着發生改變code

基礎示例代碼:

 

    public class Test1{
    public static void t1(int n){
        n = 10;
    }
    public static void t2(String s){
               s = "123";
    }
    public static void t3(int[] array){
        array[0] = 2;
    }
    public static void main(String[] args){
           int m = 5;
        1(m);
        System.out.println(m);
        String s1 = "abc";
        t2(s1);
        System.out.println(s1);
           int[] arr = {1,2,3,4};
        t3(arr);
        System.out.println(arr[0]);
    }   
   }

按照上面的參數傳遞規則,該代碼的輸出結果應該是:5 abc 2。由於int類型是按值傳遞,因此把參數m傳遞到方法t1時,至關於又複製了一份m的值,在方法t1內部修改的是複製後的值,因此m的值不變,s1的輸出和m相似。而arr是數組,屬於按址傳遞,也就是把arr的地址傳遞到了方法t3內部,在方法t3內部修改數組中的值時,原來的內容也發生改變。

以上特性是Java語言中的規定,在語法上沒法指定參數傳遞是按值傳遞仍是按址傳遞,可是能夠經過下面的變換實現:

1.對於按值傳遞的參數,若是須要在方法調用之後修改參數的值,能夠利用返回值來實現;

2.對於按值傳遞的參數,若是須要在方法內部修改時原來的參數不改變,則能夠在方法內部從新建立該對象實現。

示例代碼以下:

 

public class Test2{
    public static int t1(int n){
        n = 10;
        return n;
    }
    public static String t2(String s){
                  s = "123";
                  return  s;
    }
    public static void t3(int[] array){
        //建立新的數組並賦值
        int[] newArray = new int[array.length];
        //數據拷貝
       System.arraycopy(array,0,newArray,0,array.length);
        newArray[0] = 2;
    }
    public static void main(String[] args){
                  int m = 5;
        //從新賦值
        m = t1(m);
        System.out.println(m);
        String s1 = "abc";
        //從新賦值
        s1 = t2(s1);
        System.out.println(s1);
                  int[] arr = {1,2,3,4};
        t3(arr);
        System.out.println(arr[0]);
    }   
}

這樣,程序的輸出結果就將是:10 123 1。

在實際的程序開發中,能夠根據須要使用相似的結構來進行實現。

下面再介紹一個參數傳遞的常見應用,利用參數傳遞實現返回值,這樣的功能在IO類設計的read方法中大量使用。

示例代碼以下:

 

public class Test3{
    public static void initArray(int[] array){
                  for(int i = 0;i < array.length;i++){
                            array[i] = i;
        }
    }
    public static void main(String[] args){
                  int[] a = new int[10];
        initArray(a);
        for(int i = 0;i < a.length;i++){
                            System.out.println(a[i]);
        }
    }
}

在該示例代碼中,在initArray方法內部修改了數組的值之後,外部數組a的值也會發生改變,間接實現了返回值的效果。固然,在該示例代碼中,由於只返回一個參數,因此做用體現的不明顯,若是須要返回多個參數時,使用按址傳遞是一種不錯的主意。

因時間倉促,疏漏之處不免,請你們積極補充和指正。

相關文章
相關標籤/搜索