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+/自動補全,如下圖:

