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這三者的比較,筆者給出如下結論:
- 對於拼接少量的字串,用哪種方式都差不多,加號書寫起來更加方便。筆者幾乎沒用過concat方法。
- 加號和StringBuilder都可以拼接非字串型別(可以檢視API,有很多個append方法)。
- 對於需要拼接多個字串的時候,強烈建議使用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+/自動補全,如下圖: