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

来源:http://www.cnblogs.com/selene/archive/2016/09/17/5868160.html
-Advertisement-
Play Games

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


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

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

  註意:replaceAll傳遞的第一個參數是正則表達式  

建議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。

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

  1. 使用String類的場景:在字元串不經常變化的場景中可以使用String類,例如常量的聲明、少量的變數運算等;
  2. 使用StringBuffer的場景:在頻繁進行字元串的運算(如拼接、替換、刪除等),並且運行在多線程的環境中,則可以考慮使用StringBuffer,例如XML解析、HTTP參數解析和封裝等;
  3. 使用StringBuilder的場景:在頻繁進行字元串的運算(如拼接、替換、刪除等),並且運行在單線程的環境中,則可以考慮使用StringBuilder,如SQL語句的拼接,JSON封裝等。

  註意:在適當的場景選用字元串類型 

建議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字元串具有最高優先順序。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在我們安裝PHP模塊時,有時需要註意PHP編譯的版本,下麵講解下PHP中VC6、VC9、TS、NTS版本的區別與用法詳解,介紹php的兩種執行方式。 1. VC6與VC9的區別:VC6版本是使用Visual Studio 6編譯器編譯的,如果你的PHP是用Apache來架設的,那你就選擇VC6版本。 ...
  • \_\_init\_\_.py 文件的作用是將文件夾變為一個Python模塊,Python 中的每個模塊的包中,都有\_\_init\_\_.py 文件。 通常\_\_init\_\_.py 文件為空,但是我們還可以為它增加其他的功能。我們在導入一個包時,實際上是導入了它的\_\_init\_\_. ...
  • 從php5.0開始增加mysql(i)支持 , 新加的功能都以對象的形式添加 i表示改進的意思 功能多、效率高、穩定 編譯時參數: ./configure --with-mysql=/usr/bin/mysql_config \ #使用 Mysql ClientLibrary(libmysql)構建 ...
  • Awesome系列的Java資源整理。awesome-java 就是akullpp發起維護的Java資源列表,內容包括:構建工具、資料庫、框架、模板、安全、代碼分析、日誌、第三方庫、書籍、Java 站點等等。 業務流程管理套件 流程驅動的軟體系統構建,中間件。 jBPM:非常靈活的業務流程管理框架, ...
  • 類的基本成員才有預設值 finalize()並非析構,Java中沒有析構,使用finalize()通常在於跨語言調用情景:比如使用C進行malloc記憶體分配以後,要在finalize()方法中進行free,以便於提供一種記憶體釋放的方法,否則當量達到一定程度時會造成out of memories。Jv ...
  • 介紹一個生成動態鏈接庫*.so的例子: 首先新建1個頭文件test.h: 然後新建3個源文件first.c/second.c/third.c: first.c: second.c: third.c: 然後,生成動態鏈接庫libtest.so: gcc first.c second.c third.c ...
  • 編譯環境:windows 7 64位 編譯工具:codeblocks 13.12 備註:未使用graphics.h 聲明:個人原創,未經允許,禁止轉載!!! 數據結構:雙向鏈表 1.程式未使用graphis.h中的 函數,所以採用先清屏,再列印的方式顯示圖形,大約每秒刷新一次; 2.除蛇頭元素外,其 ...
  • 在EL表達式中,假設某個entity的status屬性為char類型,此處假設為'1',在jsp中,對於${entity.status=='1'},我們預期的結果是true,但實際上是false - -! why?是這樣的,EL表達式比較偷懶,把char類型數據做了自動轉型,所以上面的EL換一種寫法 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...