1、引入示例 html
public class StringAsParamOfMethodDemo { public static void main(String[] args) { StringAsParamOfMethodDemo sapm = new StringAsParamOfMethodDemo(); sapm.testA(); } private void testA() { String originalStr = "original"; System.out.println("=======Test A Begin:======="); System.out.println("1.The outer String:\n" + originalStr); simpleChangeString(originalStr); System.out.println("3.The outer String after inner change:\n" + originalStr); System.out.println("=======Test A End.======="); System.out.println(); } public void simpleChangeString(String original){ original = original + " is changed!"; System.out.println("2.The changed inner String:\n" + original); }
這段代碼的邏輯是這樣的:先賦值一個String類型的局部變量,而後把這個變量做爲參數送進一個方法中,在這個方法中改變該變量的值。 java
編譯運行以後,發現輸出結果是這樣的: 小程序
=======Test A Begin:======= this
1.The outer String: spa
original code
2.The changed inner String: orm
original is changed! htm
3.The outer String after inner change: 對象
original 內存
=======Test A End.=======
結果代表在方法內部對String類型的變量的從新賦值操做並無對這個變量的原型產生任何影響。
好了,這個示例的邏輯和運行結果都展現清楚了,接下來咱們來對這個小程序進行分析。在這以前咱們先來回顧下Java中所謂的「傳值」和「傳引用」問題。
2、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("1.The outer String: " + originalStr); changeNewString(originalStr); System.out.println("3.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("2.The changed inner String: " + original); }
運行結果是:
======= Test B Begin =======
1.The outer String: original
2.The changed inner String: original is changed!
3.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 testB() { String originalStr = new String("original"); System.out.println("======= Test B Begin ======="); System.out.println("1.The outer String: " + originalStr); changeNewString(originalStr); System.out.println("3.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("2.The changed inner String: " + original); }
結果以下:
=========Test C Begin=========
1.The outer String: original
2.The changed inner String: original is changed!
3.The outer String after inner change: original
=========Test C End=========
這證實了問題並非出在這,又一個解釋在實踐論據下夭折了。
那究竟是什麼緣由致使了這種情況呢?
5、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在這種狀況下的表現是相同的。
Integer例子以下:
private void testD() { Integer originalInt = new Integer(123); System.out.println("=========Test D Begin========="); System.out.println("1.The outer Integer: " + originalInt); changeIntWithMethod(originalInt); System.out.println("3.The outer Integer after inner change: " + originalInt); System.out.println("=========Test D End========="); System.out.println(); } private static void changeIntWithMethod(Integer original) { original += 456; System.out.println("2.The changed inner Integer: " + original); }
結果爲:
=========Test D Begin=========
1.The outer Integer: 123
2.The changed inner Integer: 579
3.The outer Integer after inner change: 123
=========Test D End=========