# [Java] 字串 ## 目錄 - [目錄](#%E7%9B%AE%E9%8C%84) - [前言](#%E5%89%8D%E8%A8%80) - [字串基礎](#%E5%AD%97%E4%B8%B2%E5%9F%BA%E7%A4%8E) - [字串的特性](#%E5%AD%97%E4%B8%B2%E7%9A%84%E7%89%B9%E6%80%A7) - [字串常量與字串池](#%E5%AD%97%E4%B8%B2%E5%B8%B8%E9%87%8F%E8%88%87%E5%AD%97%E4%B8%B2%E6%B1%A0) - [不可變動Immutable字串](#%E4%B8%8D%E5%8F%AF%E8%AE%8A%E5%8B%95immutable%E5%AD%97%E4%B8%B2) ## 前言 在 Java 中,字串代表一組字元,是 java.lang.String 類別的實例,Java 中的物件特性他都具備,不過字串有一些特殊的特性,是為了效能考量而設計的。 ## 字串基礎 所謂字串,就是由多個得字元組成的序列,例如 "Hello" 就是一個字串,由 H、e、l、l、o 這幾個字元組成。 在 Java 中,我們使用`"`來表示字串,例如: ```java String str = "Hello"; ``` 也因為他是字元組成的序列,所以我們可以查看它的長度,或是取得其中的字元。 ```java String str = "Hello"; System.out.println(str.length()); // 5 System.out.println(str.charAt(0)); // H ``` ## 字串的特性 ### 字串常量與字串池 開始前我們先來回達兩個問題 1. 以下的程式片段結果是 `true` 還是 `false`? ```java char[] a = {'H', 'e', 'l', 'l', 'o'}; String str1 = new String(a); String str2 = new String(a); System.out.println(str1 == str2); ``` 答案是 `false`,因為 `new` 關鍵字會在記憶體中建立一個新的物件,所以 `str1` 和 `str2` 是不同的物件。 2. 以下的程式片段結果是 `true` 還是 `false`? ```java String str1 = "Hello"; String str2 = "Hello"; System.out.println(str1 == str2); ``` 答案是 `true`,因為 Java 會將所有的字串常量放在字串池中,當我們宣告一個字串時,只要他的`序列`相同、`大小寫`相同,無倫他們出現過多少次,JVM 都只會建立一個 String 物件放在字串池中,如果你又建立了一個相同的字串,JVM 會先檢查字串池中是否已經有這個字串,如果有就直接返回這個字串的參考,不會再建立一個新的物件。如果沒有才會建立一個新的物件。 使用 `""` 來宣告字串,我們就稱他為字串常量(String Literal),字串常量是不可變動(Immutable)的,也就是說一旦建立了字串常量,他的值就不能再改變了。 而這邊我們就要特別注意,如果你想要比較兩個字串是否相等,因為前面提到的字串池及使用`new`來建立字串物件,所以我們不能使用 `==` 來比較兩個字串是否相等,因為 `==` 是比較兩個物件的參考是否相等,而不是比較兩個物件的值是否相等,所以我們應該使用 `equals()` 方法來比較兩個字串是否相等。 ```java String str1 = "Hello"; String str2 = "Hello"; String str3 = new String("Hello"); String str4 = new String("Hello"); System.out.println(str1 == str2); // true System.out.println(str1.equals(str2)); // true System.out.println(str1 == str3); // false System.out.println(str1.equals(str3)); // true System.out.println(str3 == str4); // false ``` ### 不可變動(Immutable)字串 字串常量是不可變動的,這是因為 Java 的效能考量,當我們對字串做修改時,實際上是建立了一個新的字串物件,而原本的字串物件並沒有被改變,這樣會造成記憶體的浪費,所以 Java 設計成字串是不可變動的,這樣就不用擔心字串物件被修改。 ```java String str = "Hello"; // 建立一個字串物件 str = str + " World"; // 在字串池中建立一個新的字串物件,也就是說現在有兩個字串物件,一個是 "Hello",一個是 "Hello World" System.out.println(str); // Hello World ``` 我們來寫個程式來看看。從耗費記憶體的方式到效能考量的方式。輸出1+2+3...+100的字串 ```java public class Main { public static void main(String[] args) { String str = ""; for (int i = 1; i <= 100; i++) { str += i; } System.out.println(str); } } ``` 但以上的程式碼是不好的,因為每次迴圈都會建立一個新的字串物件,這樣會造成記憶體的浪費,所以我們應該使用 StringBuilder 或 StringBuffer 來處理字串。 ```java public class Main { public static void main(String[] args) { StringBuilder str = new StringBuilder(); for (int i = 1; i <= 100; i++) { str.append(i); } System.out.println(str.toString()); } } ``` 在 Java 中,StringBuilder 和 StringBuffer 都是用來創建和操作可變的字串。它們的 API 都非常相似,但主要的區別在於 StringBuffer 是線程安全的,而 StringBuilder 不是。 - StringBuilder: - 線程不安全 - 非同步、效能較好 - 如果單線程,建議使用 StringBuilder - StringBuffer: - 線程安全 - 同步、效能較差 - 如果多線程,建議使用 StringBuffer ## 字串常用方法 Java 中的字串有很多常用的方法,我們直接使用程式碼來看看。 ```java String str = "Hello"; System.out.println(str.length()); // 5, 取得字串長度 System.out.println(str.charAt(0)); // H, 取得指定位置的字元 System.out.println(str.substring(1)); // ello, 取得指定位置之後的子字串 System.out.println(str.substring(1, 3)); // el, 取得指定位置之間的子字串 System.out.println(str.indexOf("l")); // 2, 取得指定字元第一次出現的位置 System.out.println(str.lastIndexOf("l")); // 3, 取得指定字元最後一次出現的位置 System.out.println(str.startsWith("H")); // true, 判斷字串是否以指定字元開頭 System.out.println(str.endsWith("o")); // true, 判斷字串是否以指定字元結尾 System.out.println(str.equals("Hello")); // true, 判斷字串是否與指定字串相等 System.out.println(str.equalsIgnoreCase("hello")); // true, 忽略大小寫判斷字串是否相等 System.out.println(str.toLowerCase()); // hello, 將字串轉換為小寫 System.out.println(str.toUpperCase()); // HELLO, 將字串轉換為大寫 System.out.println(str.trim()); // Hello, 移除字串前後的空白 System.out.println(str.replace("H", "h")); // hello, 取代字串中的字元 System.out.println(str.contains("l")); // true, 判斷字串是否包含指定字元 System.out.println(str.concat(" World")); // Hello World, 連接字串 System.out.println(str + " World"); // Hello World, 連接字串 ```