返回首頁

[Java] 列舉與字串

兩個常用但容易被小看的型別,放一起講。enum 不是 int 的別名,它本身就是 class——能帶欄位、帶方法,甚至能當策略物件用。String 則是 immutable,replace / toUpperCase 不會修改原物件;先理解這點,才知道 StringBuilder 是來幹嘛的。

enum 基本

enum 定義一組固定常數,本質是 class。

enum Status {
    PENDING, RUNNING, DONE, FAILED
}

Status s = Status.RUNNING;

if (s == Status.DONE) { ... }     // 用 == 比較沒問題(單例)

enum 在 switch

Status status = Status.RUNNING;
String label = switch (status) {
    case PENDING -> "等候中";
    case RUNNING -> "執行中";
    case DONE    -> "完成";
    case FAILED  -> "失敗";
};

case 裡直接寫常數名就好,不用加 Status. 前綴。

這裡是 switch expression(Java 14 正式加入),要產生完整結果,所以 enum 的常數必須涵蓋齊全,或提供 default。一般傳統 switch statement 不一定有同樣的窮舉要求;Java 21 則另外正式加入 pattern matching for switch。

enum 帶屬性與方法

enum 可以有欄位、建構子、方法,每個常數還能帶不同參數:

enum HttpStatus {
    OK(200, "OK"),
    NOT_FOUND(404, "Not Found"),
    SERVER_ERROR(500, "Internal Server Error");

    private final int code;
    private final String message;

    HttpStatus(int code, String message) {     // 建構子隱含 private
        this.code = code;
        this.message = message;
    }

    public int code() { return code; }
    public String message() { return message; }
}

HttpStatus.NOT_FOUND.code();      // 404

更進一步,每個常數可以各自覆寫方法:

enum Op {
    ADD { public int apply(int a, int b) { return a + b; } },
    MUL { public int apply(int a, int b) { return a * b; } };

    public abstract int apply(int a, int b);
}

Op.ADD.apply(2, 3);      // 5

背後的機制:每個帶大括號的常數其實是 enum 的一個匿名 subclass,{ ... } 裡的方法覆寫了 enum 層級宣告的抽象(或具象)method。所以 Op.ADD.apply(1, 2) 走的是 ADD 這個 anonymous subclass 自己的實作。

enum 內建方法

Status.values()          // 所有常數陣列
Status.valueOf("DONE")   // 字串轉 enum,找不到丟 IllegalArgumentException
s.name()                 // 常數名 "DONE"
s.ordinal()              // 在宣告中的索引(少用,重排會壞)

EnumSet / EnumMap

針對 enum 特化的集合,使用緊湊的內部表示,通常很適合這種固定範圍的 key:

EnumSet<Status> active = EnumSet.of(Status.PENDING, Status.RUNNING);
EnumSet<Status> all = EnumSet.allOf(Status.class);

EnumMap<Status, String> labels = new EnumMap<>(Status.class);
labels.put(Status.DONE, "完成");

元素/key 是 enum 時可以優先考慮這兩個;仍要看 null、並行存取等需求,不能只憑型別就一律替換。

String 是不可變的

String s = "hello";
s.toUpperCase();         // 回傳新字串,s 本身沒變
s = s.toUpperCase();     // 想改要重新賦值

操作不會改變原字串,但不保證每次配置新物件,例如無須替換時可能直接回傳原物件。迴圈中反覆累加成長中的字串,才特別需要留意配置與複製成本。

String pool

字串字面量會被放進 string pool 重用:

String a = "hello";
String b = "hello";
a == b;                  // true(同一個池中物件)

String c = new String("hello");
a == c;                  // false(new 強制建新物件)
a.equals(c);             // true(內容比較)

字串比較永遠用 .equals(),不要用 ==。

s.intern() 回傳內容相等的池中代表物件,必要時把目前物件加入池中。一般內容比較不需要先 intern,直接用 .equals() 就好。

常用 String 方法

"hello".length();             // 5
"hello".charAt(0);            // 'h'
"hello".substring(1, 4);      // "ell"(含頭不含尾)
"hello".indexOf("l");         // 2
"hello".replace('l', 'L');    // "heLLo"
"hello".contains("ell");      // true
"hello".startsWith("he");     // true
"hello".endsWith("lo");       // true
"  x  ".trim();               // "x"(去頭尾空白)
"  x  ".strip();              // "x"(Java 11+,支援 Unicode 空白)
"a,b,c".split(",");           // ["a","b","c"]
String.join("-", "a","b","c");// "a-b-c"
"abc".isEmpty();              // false
"  ".isBlank();               // true(Java 11+)
"abc".repeat(3);              // "abcabcabc"(Java 11+)
"ABC".toLowerCase();
"abc".toUpperCase();
String.valueOf(123);          // "123"
Integer.parseInt("123");      // 123

StringBuilder:可變字串

迴圈裡逐步組出一段字串,適合用 StringBuilder;少量固定的 + 拼接通常直接寫就很清楚,不必全部改掉:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i).append(",");
}
String result = sb.toString();

常用 API:

sb.append("x");
sb.insert(0, "head");
sb.delete(0, 4);
sb.reverse();
sb.setLength(0);              // 清空
sb.length();

StringBuffer 是 StringBuilder 的執行緒安全版,但幾乎用不到——單執行緒場景 StringBuilder 就夠;多執行緒下你大概也不會這樣拼字串。

字串格式化

String s = String.format("name=%s, age=%d", "Jeremy", 30);

System.out.printf("name=%s, age=%d%n", "Jeremy", 30);

常用佔位符:

意義
%s字串
%d整數
%f / %.2f浮點 / 兩位小數
%n換行(跨平台)
%5d寬度 5 右對齊
%-5d寬度 5 左對齊
%05d寬度 5 補 0
%x / %o16 / 8 進位

Java 15+ 推薦用 String.formatted,讀起來更順:

"name=%s, age=%d".formatted("Jeremy", 30);

text block(Java 15+)

三引號多行字串,自動處理共同縮排:

String json = """
    {
      "name": "Jeremy",
      "age": 30
    }
    """;

寫 SQL、JSON、HTML 都好用。要內嵌變數就配 formatted:

String sql = """
    SELECT *
    FROM users
    WHERE id = ?
    """;

SQL 範例只示範多行排版;執行時應用 PreparedStatement 綁定 userId,不要把外部輸入用 formatted 拼進查詢。一般非 SQL 文字才適合用這種方式代入內容。

char 與 Unicode 注意

char 是 16-bit,存不下所有 Unicode 字元——emoji 和部分漢字是 surrogate pair,要占 2 個 char。

"😀".length();             // 2,不是 1
"😀".codePointCount(0, "😀".length());   // 1

要正確處理就用 codePoints() stream:

"abc😀".codePoints().forEach(System.out::println);

code point 也不等於使用者看到的一個字:組合音標、膚色與多個 emoji 組成的序列可能包含多個 code point。做畫面字數或截斷時,還要考慮 grapheme cluster,不能只改用 codePointCount 就當作完全解決。

下一篇進檔案 IO——舊的 java.io 與新的 java.nio.file(Path / Files),以及為什麼新程式碼一律用後者。

參考文件

更新紀錄

  • 2026.11.05 內容更新
  • 2026.06.11 內容更新