Java-String类型的参数传递问题
http://freej.blog.51cto.com/235241/168676/
剛才看見(jiàn)一個(gè)兄弟在為Java的String傳值/傳引用問(wèn)題困惑,翻箱倒柜找到了這篇我很久以前寫(xiě)的文章,發(fā)在這里,希望能對(duì)迷惑的朋友有些幫助。提要:本文從實(shí)現(xiàn)原理的角度上闡述和剖析了:在Java語(yǔ)言中,以String作為類型的變量在作為方法參數(shù)時(shí)所表現(xiàn)出的“非對(duì)象”的特性。 一、最開(kāi)始的示例 寫(xiě)代碼最重要的就是實(shí)踐,不經(jīng)過(guò)反復(fù)試驗(yàn)而得出的說(shuō)辭只能說(shuō)是憑空遐想罷了。所以,在本文中首先以一個(gè)簡(jiǎn)單示例來(lái)拋出核心話題:
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);
????????}
????
}
? 這段代碼的邏輯是這樣的:先賦值一個(gè)String類型的局部變量,然后把這個(gè)變量作為參數(shù)送進(jìn)一個(gè)方法中,在這個(gè)方法中改變?cè)撟兞康闹怠>幾g運(yùn)行之后,發(fā)現(xiàn)輸出結(jié)果是這樣的: Test A Begin: The outer String: original The changed inner String: original is changed! The outer String after inner change: original Test A End. 這個(gè)結(jié)果表明在方法內(nèi)部對(duì)String類型的變量的重新賦值操作并沒(méi)有對(duì)這個(gè)變量的原型產(chǎn)生任何影響。好了,這個(gè)示例的邏輯和運(yùn)行結(jié)果都展示清楚了,接下來(lái)我們來(lái)對(duì)這個(gè)小程序進(jìn)行分析。在這之前我們先來(lái)回顧下Java中所謂的“傳值”和“傳引用”問(wèn)題。 二、Java中的“傳值”和“傳引用”問(wèn)題 許多初學(xué)Java的程序員都在這個(gè)問(wèn)題上有所思索,那是因?yàn)檫@是所謂的“C語(yǔ)言的傳值和傳指針問(wèn)題”在Java語(yǔ)言上同類表現(xiàn)。 最后得出的結(jié)論是: 在Java中,當(dāng)基本類型作為參數(shù)傳入方法時(shí),無(wú)論該參數(shù)在方法內(nèi)怎樣被改變,外部的變量原型總是不變的,代碼類似上面的示例:
int number = 0;
changeNumber(number) {number++}; //改變送進(jìn)的int變量
System.out.println(number); //這時(shí)number依然為0 這就叫做“值傳遞”,即方法操作的是參數(shù)變量(也就是原型變量的一個(gè)值的拷貝)改變的也只是原型變量的一個(gè)拷貝而已,而非變量本身。所以變量原型并不會(huì)隨之改變。 ?????????????????? 但當(dāng)方法傳入的參數(shù)為非基本類型時(shí)(也就是說(shuō)是一個(gè)對(duì)象類型的變量),方法改變參數(shù)變量的同時(shí)變量原型也會(huì)隨之改變,代碼同樣類似上面的示例:
StringBuffer strBuf = new StringBuffer(“original”);
changeStringBuffer(strBuf) {strbuf.apend(“ is changed!”)} //改變送進(jìn)的StringBuffer變量
System.out.println(strBuf); //這時(shí)strBuf的值就變?yōu)榱薿riginal is changed!???? 這種特性就叫做“引用傳遞”,也叫做傳址,即方法操作參數(shù)變量時(shí)是拷貝了變量的引用,而后通過(guò)引用找到變量(在這里是對(duì)象)的真正地址,并對(duì)其進(jìn)行操作。當(dāng)該方法結(jié)束后,方法內(nèi)部的那個(gè)參數(shù)變量隨之消失。但是要知道這個(gè)變量只是對(duì)象的一個(gè)引用而已,它只是指向了對(duì)象所在的真實(shí)地址,而非對(duì)象本身,所以它的消失并不會(huì)帶來(lái)什么負(fù)面影響。回頭來(lái)看原型變量,原型變量本質(zhì)上也是那個(gè)對(duì)象的一個(gè)引用(和參數(shù)變量是一樣一樣的),當(dāng)初對(duì)參數(shù)變量所指對(duì)象的改變就根本就是對(duì)原型變量所指對(duì)象的改變。所以原型變量所代表的對(duì)象就這樣被改變了,而且這種改變被保存了下來(lái)。 ???????? 了解了這個(gè)經(jīng)典問(wèn)題,很多細(xì)心的讀者肯定會(huì)立刻提出新的疑問(wèn):“可是String類型在Java語(yǔ)言中屬于非基本類型啊!它在方法中的改變?yōu)槭裁礇](méi)有被保存下來(lái)呢!”的確,這是個(gè)問(wèn)題,而且這個(gè)新疑問(wèn)幾乎推翻了那個(gè)經(jīng)典問(wèn)題的全部結(jié)論。真是這樣么?好,現(xiàn)在我們就來(lái)繼續(xù)分析。 三、關(guān)于String參數(shù)傳遞問(wèn)題的曲解之一——直接賦值與對(duì)象賦值 String類型的變量作為參數(shù)時(shí)怎么會(huì)像基本類型變量那樣以傳值方式傳遞呢?關(guān)于這個(gè)問(wèn)題,有些朋友給出過(guò)解釋,但可惜并不正確。 一種解釋就是,對(duì)String類型的變量賦值時(shí)并沒(méi)有new出對(duì)象,而是直接用字符串賦值,所以Java就把這個(gè)String類型的變量當(dāng)作基本類型看待了。即,應(yīng)該String str = new String(“original”);,而不是String str = “original”;。這是問(wèn)題所在么?我們來(lái)為先前的示例稍微改造下,運(yùn)行之后看看結(jié)果就知道了。改造后的代碼如下: ? ????????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);
???????????? }
?
我們來(lái)看看這次運(yùn)行結(jié)果是怎么樣的: Test B Begin: The outer String: original The changed inner String: original is changed! The outer String after inner change: original Test B End. 實(shí)踐證明,這種說(shuō)法是錯(cuò)的。 實(shí)際上,字符串直接賦值和用new出的對(duì)象賦值的區(qū)別僅僅在于存儲(chǔ)方式不同。 簡(jiǎn)單說(shuō)明下: 字符串直接賦值時(shí),String類型的變量所引用的值是存儲(chǔ)在類的常量池中的。因?yàn)?span style="font-family:Calibri">”original”本身是個(gè)字符串常量,另一方面String是個(gè)不可變類型,所以這個(gè)String類型的變量相當(dāng)于是對(duì)一個(gè)常量的引用。這種情況下,變量的內(nèi)存空間大小是在編譯期就已經(jīng)確定的。 而new對(duì)象的方式是將”original”存儲(chǔ)到String對(duì)象的內(nèi)存空間中,而這個(gè)存儲(chǔ)動(dòng)作是在運(yùn)行期進(jìn)行的。在這種情況下,Java并不是把”original”這個(gè)字符串當(dāng)作常量對(duì)待的,因?yàn)檫@時(shí)它是作為創(chuàng)建String對(duì)象的參數(shù)出現(xiàn)的。 所以對(duì)String的賦值方式和其參數(shù)傳值問(wèn)題并沒(méi)有直接聯(lián)系。總之,這種解釋并不是正解。 四、關(guān)于String參數(shù)傳遞問(wèn)題的曲解之二——“=”變值與方法變值 又有些朋友認(rèn)為,變值不同步的問(wèn)題是處在改變值的方式上。 這種說(shuō)法認(rèn)為:“在Java 中,改變參數(shù)的值有兩種情況,第一種,使用賦值號(hào)“=”直接進(jìn)行賦值使其改變;第二種,對(duì)于某些對(duì)象的引用,通過(guò)一定途徑對(duì)其成員數(shù)據(jù)進(jìn)行改變,如通過(guò)對(duì)象的本身的方法。對(duì)于第一種情況,其改變不會(huì)影響到被傳入該參數(shù)變量的方法以外的數(shù)據(jù),或者直接說(shuō)源數(shù)據(jù)。而第二種方法,則相反,會(huì)影響到源數(shù)據(jù)——因?yàn)橐弥甘镜膶?duì)象沒(méi)有變,對(duì)其成員數(shù)據(jù)進(jìn)行改變則實(shí)質(zhì)上是改變的該對(duì)象。” 這種方式聽(tīng)起來(lái)似乎有些…,我們還是用老辦法,編寫(xiě)demo,做個(gè)小試驗(yàn),代碼如下: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);
} ? 結(jié)果如下: ? Test C Begin: The outer String: original The changed inner String: original is changed! The outer String after inner change: original Test C End. ? 怎么樣,這證明了問(wèn)題并不是出在這,又一個(gè)解釋在實(shí)踐論據(jù)下夭折了。 那到底是什么原因?qū)е铝诉@種狀況呢? 好了,不賣關(guān)子了,下面說(shuō)下我的解釋。 ? 五、String參數(shù)傳遞問(wèn)題的癥結(jié)所在 其實(shí),要想真正理解一個(gè)類或者一個(gè)API/框架的最直接的方法就是看源碼。 下面我們來(lái)看看new出String對(duì)象的那小段代碼(String類中),也就是String類的構(gòu)造函數(shù): ?
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[],這說(shuō)明對(duì)String的存儲(chǔ)實(shí)際上通過(guò)char[]來(lái)實(shí)現(xiàn)的。怎么樣?其實(shí)就是一層窗戶紙。不知道大家還記不記得在Java API中定義的那些基本類型的包裝類。比如Integer是int包裝類、Float是float的包裝類等等。對(duì)這些包裝類的值操作實(shí)際上都是通過(guò)對(duì)其對(duì)應(yīng)的基本類型操作而實(shí)現(xiàn)的。是不是有所感悟了?對(duì),String就相當(dāng)于是char[]的包裝類。包裝類的特質(zhì)之一就是在對(duì)其值進(jìn)行操作時(shí)會(huì)體現(xiàn)出其對(duì)應(yīng)的基本類型的性質(zhì)。在參數(shù)傳遞時(shí),包裝類就是如此體現(xiàn)的。所以,對(duì)于String在這種情況下的展現(xiàn)結(jié)果的解釋就自然而然得出了。同樣的,Integer、Float等這些包裝類和String在這種情況下的表現(xiàn)是相同的,具體的分析在這里就省略了,有興趣的朋友可以自己做做試驗(yàn)。 這也就是為什么當(dāng)對(duì)字符串的操作在通過(guò)不同方法來(lái)實(shí)現(xiàn)的時(shí)候,推薦大家使用StringBuffer的真正原因了。至于StringBuffer為什么不會(huì)表現(xiàn)出String這種現(xiàn)象,大家再看看的StringBuffer的實(shí)現(xiàn)就會(huì)明白了,在此也不再贅述了。 六、寫(xiě)在最后 由此String類型的參數(shù)傳遞問(wèn)題的原理也就展現(xiàn)出來(lái)了。其實(shí)可以看出,只要分析方式正確,思考終究得出正確結(jié)論的。 正確分析方法的基礎(chǔ)有二: 1、多實(shí)踐:手千萬(wàn)不要犯懶,實(shí)踐必會(huì)出真知。 2、基于原理:搞清楚程序邏輯的最直接最簡(jiǎn)單的方式就是看源碼,這毋庸置疑。 只要基于這兩個(gè)基礎(chǔ)進(jìn)行分析,在很多情況下會(huì)達(dá)到事半功倍的效果。這算是經(jīng)驗(yàn)之談吧,也算是分析程序的“捷徑”方式之一。
?
===============http://developer.51cto.com/art/200812/102523.htm
和其它程序設(shè)計(jì)語(yǔ)言類似,Java語(yǔ)言的參數(shù)傳遞也分為兩種:
1.按值傳遞(by value)
適用范圍:8種基本數(shù)據(jù)類型、String對(duì)象
特點(diǎn):在內(nèi)存中復(fù)制一份數(shù)據(jù),把復(fù)制后的數(shù)據(jù)傳遞到方法內(nèi)部
作用:在方法內(nèi)部改變參數(shù)的值,外部數(shù)據(jù)不會(huì)跟著發(fā)生改變
2.按址傳遞(by address)
適用范圍:數(shù)組、除String以外的其他所有類型的對(duì)象
特點(diǎn):將對(duì)象的地址傳遞到方法內(nèi)部
作用:在方法內(nèi)部修改對(duì)象的內(nèi)容,外部數(shù)據(jù)也會(huì)跟著發(fā)生改變
基礎(chǔ)示例代碼:
| 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]);} } |
按照上面的參數(shù)傳遞規(guī)則,該代碼的輸出結(jié)果應(yīng)該是:5 abc 2。因?yàn)閕nt類型是按值傳遞,所以把參數(shù)m傳遞到方法t1時(shí),相當(dāng)于又復(fù)制了一份m的值,在方法t1內(nèi)部修改的是復(fù)制后的值,所以m的值不變,s1的輸出和m類似。而arr是數(shù)組,屬于按址傳遞,也就是把a(bǔ)rr的地址傳遞到了方法t3內(nèi)部,在方法t3內(nèi)部修改數(shù)組中的值時(shí),原來(lái)的內(nèi)容也發(fā)生改變。
以上特性是Java語(yǔ)言中的規(guī)定,在語(yǔ)法上無(wú)法指定參數(shù)傳遞是按值傳遞還是按址傳遞,但是可以通過(guò)下面的變換實(shí)現(xiàn):
1.對(duì)于按值傳遞的參數(shù),如果需要在方法調(diào)用以后修改參數(shù)的值,可以利用返回值來(lái)實(shí)現(xiàn);
2.對(duì)于按值傳遞的參數(shù),如果需要在方法內(nèi)部修改時(shí)原來(lái)的參數(shù)不改變,則可以在方法內(nèi)部重新創(chuàng)建該對(duì)象實(shí)現(xiàn)。
示例代碼如下:
| 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){//創(chuàng)建新的數(shù)組并賦值int[] newArray = new int[array.length];//數(shù)據(jù)拷貝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]);} } |
這樣,程序的輸出結(jié)果就將是:10 123 1。
在實(shí)際的程序開(kāi)發(fā)中,可以根據(jù)需要使用類似的結(jié)構(gòu)來(lái)進(jìn)行實(shí)現(xiàn)。
下面再介紹一個(gè)參數(shù)傳遞的常見(jiàn)應(yīng)用,利用參數(shù)傳遞實(shí)現(xiàn)返回值,這樣的功能在IO類設(shè)計(jì)的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方法內(nèi)部修改了數(shù)組的值以后,外部數(shù)組a的值也會(huì)發(fā)生改變,間接實(shí)現(xiàn)了返回值的效果。當(dāng)然,在該示例代碼中,因?yàn)橹环祷匾粋€(gè)參數(shù),所以作用體現(xiàn)的不明顯,如果需要返回多個(gè)參數(shù)時(shí),使用按址傳遞是一種不錯(cuò)的主意。
因時(shí)間倉(cāng)促,疏漏之處難免,請(qǐng)大家積極補(bǔ)充和指正。
?
總結(jié)
以上是生活随笔為你收集整理的Java-String类型的参数传递问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 情商包括哪些方面
- 下一篇: velocity显示List与Map的方