編寫高品質代碼:改善Java程式的151個建議(第4章:字串___建議52~55),java151

來源:互聯網
上載者:User

編寫高品質代碼:改善Java程式的151個建議(第4章:字串___建議52~55),java151
建議52:推薦使用String直接量賦值

  一般對象都是通過new關鍵字產生的,但是String還有第二種產生方式,也就是我們經常使用的直接聲明方式,這種方式是極力推薦的,但不建議使用new String("A")的方式賦值。為什麼呢?我們看如下代碼:

public class Client58 {    public static void main(String[] args) {        String str1 = "詹姆斯";        String str2 = "詹姆斯";        String str3 = new String("詹姆斯");        String str4 = str3.intern();        // 兩個直接量是否相等        System.out.println(str1 == str2);        // 直接量和對象是否相等        System.out.println(str1 == str3);        // 經過intern處理後的對象與直接量是否相等        System.out.println(str1 == str4);    }}

  注意看上面的程式,我們使用"=="判斷的是兩個對象的引用地址是否相同,也就是判斷是否為同一個對象,列印的結果是true,false,true。即有兩個直接量是同一個對象(進過intern處理後的String與直接量是同一個對象),但直接通過new產生的對象卻與之不等,原因何在?

  原因是Java為了避免在一個系統中大量產生String對象(為什麼會大量產生,因為String字串是程式中最經常使用的類型),於是就設計了一個字串池(也叫作字串常量池,String pool或String Constant Pool或String Literal Pool),在字串池中容納的都是String字串對象,它的建立機制是這樣的:建立一個字串時,首先檢查池中是否有字面值相等的字串,如果有,則不再建立,直接返回池中該對象的引用,若沒有則建立之,然後放到池中,並返回建立對象的引用,這個池和我們平常說的池非常接近。對於此例子來說,就是建立第一個"詹姆斯"字串時,先檢查字串池中有沒有該對象,發現沒有,於是就建立了"詹姆斯"這個字串並放到池中,待建立str2字串時,由於池中已經有了該字串,於是就直接返回了該對象的引用,此時,str1和str2指向的是同一個地址,所以使用"=="來判斷那當然是相等的了。

  那為什麼使用new String("詹姆斯")就不相等了呢?因為直接聲明一個String對象是不檢查字串池的,也不會把對象放到字串池中,那當然"=="為false了。

  那為什麼intern方法處理後即又相等了呢?因為intern會檢查當前對象在對象池中是否存在字面值相同的引用對象,如果有則返回池中的對象,如果沒有則放置到對象池中,並返回當前對象。

  可能有人要問了,放到池中,是不是要考慮記憶體回收問題呀?不用考慮了,雖然Java的每個對象都儲存在堆記憶體中但是字串非常特殊,它在編譯期已經決定了其存在JVM的常量池(Constant Pool),記憶體回收不會對它進行回收的。

  通過上面的介紹,我們發現Java在字串的建立方面確實提供了非常好的機制,利用對象池不僅可以提高效率,同時減少了記憶體空間的佔用,建議大家在開發中使用直接量賦值方式,除非必要才建立一個String對象。

建議53:注意方法中傳遞的參數要求

   有這樣的一個簡單需求,寫一個方法,實現從原始字串中刪除與之匹配的所有字串,比如在"好是好"中,刪除"好",代碼如下:

public class StringUtils {    //刪除字串    public static String remove(String source, String sub) {        return source.replaceAll(sub, "");    }}

  StringUtils工具類很簡單,它採用了String的replaceAll方法,該方法是做字串替換的,我們編寫一個測試案例,檢查remove方法是否正確,如下所示:

import static org.junit.Assert.*;import org.junit.Test;public class TestStringUtils {    @Test    public void test() {        assertTrue(StringUtils.remove("好是好","好").equals("是"));        assertTrue(StringUtils.remove("$是$","$").equals("是"));    }}

  單獨運行第一個是綠條,單獨運行第二個是紅條,為什麼第二個(assertTrue(StringUtils.remove("$是$","$").equals("是")))不通過呢?

  問題就出在replaceAll方法上,該方法確實需要傳遞兩個String類型的參數,也確實進行了字串替換,但是它要求第一個參數是Regex,符合Regex的字串才會被替換。對上面的例子來說,第一個測試案例傳遞進來的是一個字串"好",這是一個全匹配尋找替換,處理的非常正確,第二個測試案例傳遞進來的是一個"$"符號,"$"符號在Regex中表示的是字串的結束位置,也就是執行完replaceAll後在字串結尾的地方加上了Null 字元串,其結果還是"$"是"$",所以測試失敗也就再所難免了。問題清楚了,解決方案也就出來了:使用replace方法替換即可,它是replaceAll的方法的簡化版,可傳遞兩個String參數,與我們的編碼意圖是吻合的。

  大家如果注意看JDK文檔,會發現replace(CharSequence target,CharSequence replacement)方法是1.5版本以後才開始提供的, 在此之前如果要對一個字串進行全體換,只能使用replaceAll方法,不過由於replaceAll方法的第二個參數使用了Regex,而且參數類型只要是CharSequence就可以(String的父類),所以很容易使使用者誤解,稍有不慎就會導致嚴重的替換錯誤。

  注意:replaceAll傳遞的第一個參數是Regex  

建議54:正確使用String、StringBuffer、StringBuilder

   CharSequence介面有三個實作類別與字串有關,String、StringBuffer、StringBuilder,雖然它們都與字串有關,但其處理機制是不同的。

  String類是不可變的量,也就是建立後就不能再修改了,比如建立了一個"abc"這樣的字串對象,那麼它在記憶體中永遠都會是"abc"這樣具有固定表面值的一個對象,不能被修改,即使想通過String提供的方法來嘗試修改,也是要麼建立一個新的字串對象,要麼返回自己,比如:

String  str = "abc";String str1 = str.substring(1);

  其中str是一個字串對象,其值是"abc",通過substring方法又重建了一個字串str1,它的值是"bc",也就是說str引用的對象一但產生就永遠不會變。為什麼上面還說有可能不建立對象而返回自己呢?那是因為採用substring(0)就不會建立對象。JVM從字串池中返回str的引用,也就是自身的引用。

  StringBuffer是一個可變字串,它與String一樣,在記憶體中儲存的都是一個有序的字元序列(char 類型的數組),不同點是StringBuffer對象的值是可改變的,例如:

StringBuffer sb = new StringBuffer("a");sb.append("b");

  從上面的代碼可以看出sb的值在改變,初始化的時候是"a" ,經過append方法後,其值變成了"ab"。可能有人會問了,這與String類通過 "+" 串連有什麼區別呢?例如

String s = "a";s = s + "b";

  有區別,字串變數s初始化時是 "a" 對象的引用,經過加號計算後,s變數就修改為了 “ab” 的引用,但是初始化的 “a” 對象還沒有改變,只是變數s指向了新的引用地址,再看看StringBuffer的對象,它的引用地址雖不變,但值在改變。

  StringBuffer和StringBuilder基本相同,都是可變字元序列,不同點是:StringBuffer是安全執行緒的,StringBuilder是線程不安全的,翻翻兩者的原始碼,就會發現在StringBuffer的方法前都有關鍵字syschronized,這也是StringBuffer在效能上遠遠低於StringBuffer的原因。

  在效能方面,由於String類的操作都是產生String的對象,而StringBuilder和StringBuffer只是一個字元數組的再擴容而已,所以String類的操作要遠慢於StringBuffer 和 StringBuilder。

  弄清楚了三者之間的原理,我們就可以在不同的情境下使用不同的字元序列了:

  注意:在適當的情境選用字串類型 

建議55:注意字串的位置

   看下面一段程式:

public class Client55 {    public static void main(String[] args) {        String str1 = 1 + 2 + "apples";        String str2 = "apples" + 1 + 2;        System.out.println(str1);        System.out.println(str2);    }}

  想想兩個字串輸出的結果的蘋果數量是否一致,如果一致,會是幾呢?

  答案是不一致,str1的值是"3apples" ,str2的值是“apples12”,這中間懸殊很大,只是把“apples” 調換了一下位置,為何會發生如此大的變化呢?

  這都源於java對於加號的處理機制:在使用加號進行計算的運算式中,只要遇到String字串,則所有的資料都會轉換為String類型進行拼接,如果是未經處理資料,則直接拼接,如是是對象,則調用toString方法的傳回值然後拼接,如:

  str =  str + new ArrayList();

  上面就是調用ArrayList對象的toString方法傳回值進行拼接的。再回到前面的問題上,對與str1 字串,Java的執行順序是從左至右,先執行1+2,也就是算術加法運算,結果等於3,然後再與字串進行拼接,結果就是 "3 apples",其它形式類似於如下計算:

  String str1 = (1 + 2 ) + "apples" ;

  而對於str2字串,由於第一個參與運算的是String類型,加1後的結果是“apples 1” ,這仍然是一個字串,然後再與2相加,結果還是一個字串,也就是“apples12”。這說明如果第一個參數是String,則後續的所有計算都會轉變為String類型,誰讓字串是老大呢!

  注意: 在“+” 運算式中,String字串具有最高優先順序。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.