兩個常用但容易被小看的型別,放一起講。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 / %o | 16 / 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 內容更新

