《Java從入門到失業》第四章:類和物件(4.2):String類

Java大失叔發表於2020-09-14

4.2String類

       這一節,我們學習第一個類:String類。String翻譯成漢語就是“字串”,是字元的序列。我們知道,在Java中,預設採用Unicode字符集,因此字串就是Unicode字元的序列。例如字串“Java大失叔”,就是由7個Unicode字元‘J’、‘a’、‘v’、‘a’、‘大’、‘失’、‘叔’組成。在JDK中,把字串抽象成一個類String提供給我們使用。String類在java.lang包中。

4.2.1構造String物件

  上面我們說了,想看電視得先買一臺電視,電視在出廠的時候廠家會初始化它的狀態。想使用String類,得先得到一個String的物件,然後指定屬性的初始狀態,然後才能使用它。得到物件的過程,叫做構造物件。在Java中,我們用構造器(constructor)來構造例項,構造器其實是一種特殊的方法,用來構造並初始化物件。我們採用在構造器前面加上new關鍵字來實現,例如:

new 構造器();

我們檢視String類的API文件(怎麼查這裡不再贅述),構造方法截圖如下:

 

發現String類的構造方法有幾個特點:

  • 足足有15個構造方法
  • 有的方法上標有Deprecated,這個標籤的含義是不推薦使用,將來在新版本中可能會移除
  • 構造方法的名字和類名相同

構造方法的名字和類名相同,這是Java構造器的特點,也是規定。我們挑選其中一個構造方法:String(char[] value)

我們看到,這其實就是用一個char陣列來構造一個字串,那麼首先我們得有一個char陣列才行,例如我們想要得到一個字串“Java大失叔,你真棒”。那麼程式碼如下:

char[] a = { 'J', 'a', 'v', 'a', '大', '失', '叔', ',', '你', '真', '棒' };  
String s = new String(a);  
System.out.println(s);// 結果輸出:Java大失叔,你真棒 

事實上,由於String太常用了,Java給我們提供了更加簡便的構造方法,直接用雙引號將一段字元序列包起來,就得到了一個String的例項:

String s = "Java大失叔,你真棒";  

OK,我們得到了一個String物件了,下面我們來使用這個物件。我們可以看到,API中有幾十個方法,我們挑選一些常用的演示一下。

4.2.2程式碼點和程式碼單元

       首先,我們回憶一下關於char和Unicode的知識。Unicode定義了U+0000到U+10FFFF一共1114112個碼位(code point),英文直譯為程式碼點。一個程式碼點表示一個字元。char是用來存放UTF-16編碼中的一個程式碼單元(code unit),即2個位元組。平面0的程式碼點用一個程式碼單元即一個char就可以表示,其餘的程式碼點需要用2個程式碼單元即2個char才能表示。

       我們知道Stirng是Unicode字元的序列,但是底層的實現實際上是用char構成的。String類提供了一些關於程式碼點和程式碼單元相關的方法,請看下面摘抄的幾個方法:

修飾和型別

方法

描述

int

length()

返回字串的長度

int

codePointCount(int beginIndex, int endIndex)

返回beginIndex和endIndex-1之間的程式碼點的數量。

char

charAt(int index)

返回index索引處的char

int

codePointAt(int index)

返回index索引處的程式碼點

我們想獲得字元的數量(即程式碼點的數量),需要用codePointCount方法,而length方法返回的是char的數量(即程式碼單元的數量)。呼叫物件的方法很簡單,用如下形式:

物件.方法();

程式碼示例如下:

String s = "大失叔喜歡打麻將??????";//  
System.out.println("字串s的程式碼單元數量為:" + s.length());  
System.out.println("字串s的程式碼點數量為" + s.codePointCount(0, s.length())); 

輸出結果:

字串s的程式碼單元數量為:20  
字串s的程式碼點數量為:14 

我們可以看到,對於??????,這6個字元,每個字元佔用2個程式碼單元,所以length方法的結果是20,而codePointCount方法的結果是14。

       我們再看看後面2個方法,這應該就相對簡單了,一個是返回index處的程式碼單元,一個是返回index處的程式碼點。我們直接看程式碼:

String s = "大失叔喜歡打麻將??????";//  
int c = s.charAt(8);// 把char賦值給一個int,對應這個程式碼單元對應的十進位制,結果是55356,十六進位制為0xD83C
int d = s.codePointAt(8);// 結果是126976,十六進位制為0x1F000 
 

4.2.3物件與變數

       上面我們看到,建立出來一個String物件,一般我們會賦值給一個變數。那麼物件和變數之間有什麼關係和區別呢?我們先看幾行程式碼:

String a; 
String b;
a = "大失叔喜歡打麻將";  
b = a;
 

這幾行程式碼,會涉及到下面一些行為:

  • 第1、2行,我們定義了2個String型別的變數a和b。這時候Java會在記憶體中分別分配一塊空間給a和b,但是這時候這2塊記憶體空間中沒有存放任何值。
  • 第3行,我們把一個字串賦值給變數a。Java會在記憶體中分配一塊空間,存放這個字串,然後把這塊空間的地址存放到變數a的記憶體空間中。
  • 第4行,把變數a賦值給b,相當於把變數a記憶體空間中的地址存放到變數b的記憶體空間中,這時候a和b同時指向字串“大失叔喜歡打麻將”對應的記憶體空間。

我們用一張圖示意如下:

 

我們需要牢牢記住一點:在Java中,任何物件的值都是存放在堆記憶體中的,而物件型別的變數對應的記憶體中儲存的是物件的記憶體地址,我們稱之為物件引用。因此new操作符返回的結果其實是一個引用。

       我們可以顯式的把一個物件變數設定為null,這時候該變數的記憶體存放的將是空值,表明它不引用任何物件。如果我們對一個值為null的變數進行方法呼叫,程式在執行時則會丟擲異常。

4.2.4字串拼接

       在Java中,字串的拼接有一種很簡單的方法,就是用加號(+)連線兩個字串,結果會構造出一個新的字串物件。我們看程式碼:

String a = "Java大失叔";  
String b = "喜歡打麻將";  
String c = a + b;  
System.out.println(c);// 結果將輸出:"Java大失叔喜歡打麻將"

在這段程式碼中,堆記憶體中將會分配3塊空間,分別對應字串"Java大失叔"、"喜歡打麻將"、" Java大失叔喜歡打麻將"。我們用一張圖來演示這個過程:

 

我們還可以將一個字串和一個非字串用+連線起來,這時候非字串物件會被轉換為字串(具體如何轉換,後續會詳細探討)。例如:

String a = "Java大失叔卡里只有";  
int b = 200;  
String c = "元錢了";  
System.out.println(a + b + c);// 結果將輸出:Java大失叔卡里只有200元錢了

String類的API中還提供了一個方法concat用來拼接字串,方法摘抄如下:

修飾和型別

方法

描述

String

concat(String str)

將str拼接在本字串後面

使用起來也很簡單,程式碼如下:

String a = "Java大失叔";  
String b = "喜歡打麻將";  
String c = a.concat(b);  
System.out.println(c);// 結果將輸出:Java大失叔喜歡打麻將

  有的時候,需要將很多個字串拼接成一個大字串,這時,如果用+的方式,不是很合適了。因為用+的方式,每次都會構建一個新的物件,比較耗時,還佔記憶體,效率比較低。好在Java提供了另外一種方式,就是採用StringBuilder類和StringBuffer類。一般情況下我們都會採用StringBuilder類,因為它的效率略高。而Stringbuffer類是執行緒安全的,關於執行緒會在後面專門討論。這2個類的API幾乎完全一樣。用StringBuilder非常簡單,程式碼演示如下:

StringBuilder sb = new StringBuilder();// 首先構建StringBuilder物件  
sb.append("Java");// 然後用append方法新增小字串  
sb.append("大失叔");  
sb.append("太帥了");  
String s = sb.toString();// 最後呼叫toString()方法,返回一個字串物件  
System.out.println(s);// 結果將輸出:Java大失叔太帥了

其實append方法返回的依然是StringBuilder物件,因此還可以採用一種更為簡潔的方式:

String s = new StringBuilder().append("Java").append("大失叔").append("太帥了").toString();  
System.out.println(s);// 結果將輸出:Java大失叔太帥了

關於加號、concat、StringBuilder這三者的比較,筆者給出如下結論:

  1. 對於拼接少量的字串,用哪種方式都差不多,加號書寫起來更加方便。筆者幾乎沒用過concat方法。
  2. 加號和StringBuilder都可以拼接非字串型別(可以檢視API,有很多個append方法)。
  3. 對於需要拼接多個字串的時候,強烈建議使用StringBuilder。(筆者在早年編寫一個網路程式的時候,吃過虧)

4.2.5字串擷取和比較

       關於字串還會經常使用比較和擷取的方法,先列出方法如下:

修飾和型別

方法

描述

boolean

startsWith(String prefix)

檢查字串是否以指定的字首prefix開始

boolean

endsWith(String suffix)

檢查字串是否以指定的字尾suffix結尾

String

trim()

刪除字串前後的空白,並返回一個新字串

boolean

equals(Object anObject)

檢測2個字串是否相等

boolean

equalsIgnoreCase(String anotherString)

檢測2個字串在忽略大小寫的情況下是否相等

String

substring(int beginIndex)

擷取從beginIndex到末尾的字串並返回

String

substring(int beginIndex, int endIndex)

擷取從beginIndex到endIndex的字串並返回,不包括endIndex

我們經常會比較一個字串是否以某個字串開頭或結尾,程式碼如下:

String a = "Java大失叔";  
boolean b1 = a.startsWith("Java");// 結果為true  
boolean b2 = a.startsWith("java");// 結果為false  
boolean b3 = a.endsWith("叔");// 結果為true  

  有時候,經過網路傳輸後的字串經常前後會帶一些空白,眼睛又看不見,很不利於比較,會用trim方法去掉前後的空白:

String a = "   Java大失叔       ";  
String b = a.trim();  
System.out.println(b);// 結果將輸出:Java大失叔 

需要注意,這裡的空白指的是Unicode編碼小於或等於”\u0020”的字元。

       對於字串的擷取,用subString方法將非常方便:

String a = "Java大失叔 ";  
String b = a.substring(4);// 結果是:大失叔  
String c = a.substring(2, 6);// 結果是:va大失

這裡要注意的是,返回的結果字串是包括beginIndex位置的程式碼單元,但是不包括endIndex位置的程式碼單元。

       比較2個字串是否相等,用equals方法,如果相等返回ture,否則返回false。如果想不區分大小寫比較是否相等,則可以使用equalsIgnoreCase方法。表示式為:

a.equals(b)

其中,a和b即可以是變數,也可以是字串常量。

String a = "Java大失叔";  
String b = "java大失叔";  
System.out.println(a.equals(b));// 結果為false  
System.out.println(a.equalsIgnoreCase(b));// 結果為true
System.out.println("JAVA大失叔".equalsIgnoreCase(b));// 結果為true

這裡需要特別注意,千萬不能用==運算子來比較2個字串是否相等。因為==運算子比較的是2個字串是否存放在同一個記憶體位置上。但是事實上,對於2個字元內容完全一樣的字串,是很有可能存放在不同的記憶體空間的,因此用==比較結果將為false。這個問題Java新手經常會犯。

  最後我們很容易發現,String的API中沒有提供修改字串內容的方法。這其實是因為String類被定義為final的(關於final後面也會介紹),我們看一下String的原始碼(在Eclipse中,可以很輕鬆的檢視原始碼,滑鼠移動的任意一個String字元上,按住Ctrl鍵後,點選滑鼠左鍵):

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence 

用final修飾一個類後,這個類的物件將不能被修改。

       String類提供了50個多個方法,這些方法都很有用,但是我們不可能記住所有的方法名和引數要求,這裡還有一個Eclipse的小技巧,當我們敲完變數名加“點”後,Eclipse會自動彈出提示,或者還可以用Ctrl+/自動補全,如下圖:

 

相關文章