String 不可变
String s = "hello";
s.toUpperCase(); // 返回 "HELLO",s 没变
System.out.println(s); // hello
s = s.toUpperCase(); // 必须赋值
每个 String 操作返回新 String——原 String 永远不动。这是 Java 设计:线程安全、缓存哈希、池化共享。
文本块(Java 15+)
String json = """
{
"name": "Alice",
"age": 30
}
""";
三个或更多 """ 围起来。结尾 """ 的缩进决定基线,每行该缩进会被去掉。
老写法(仍可用,但难看):
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
转义
String s = "Tab\there\nnewline";
String path = "C:\\Users\\Alice"; // \\ 表示一个 \
String quote = "She said \"hi\"";
文本块里几乎不用转义——双引号直接写,反斜杠也是字面:
String s = """
She said "hi"
C:\Users\Alice
""";
拼接
String s1 = "Hello, " + name; // 短拼接
String s2 = "Hello, " + name + "! You are " + age + ".";
// 大量拼接:StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(",");
}
String result = sb.toString();
// 数组 → 一个字符串
String csv = String.join(", ", new String[]{"a", "b", "c"}); // "a, b, c"
// 多个值
String s3 = String.join(", ", List.of("a", "b", "c"));
为什么大循环用 StringBuilder?因为 String 不可变——s += x 每次新建对象,性能 O(n²)。
单语句的字符串
+编译器会优化成 StringBuilder。但跨语句 / 循环就不会——所以循环里要手写 StringBuilder。
String.format / formatted
// 静态方法
String s = String.format("Hello %s, you are %d.", name, age);
String s2 = String.format("%.2f", Math.PI); // "3.14"
String s3 = String.format("%05d", 42); // "00042"
// 实例方法(Java 15+)
String s4 = "Hello %s, you are %d.".formatted(name, age);
// 常用格式说明符
%s 字符串
%d 整数
%f 浮点
%.2f 保留 2 位小数
%05d 补 0 到 5 位
%x 十六进制(小写)
%X 十六进制(大写)
%b 布尔
%n 平台换行(推荐替代 \n)
没有内置插值
Java 没有 $"{name}" 这种插值。最接近的是:
// Java 21+ 预览:String Template
String msg = STR."Hello \{name}, you are \{age}";
但21 起预览、22 废止、23 重新设计、25 尚未稳定——别用,可能再变。
实际项目:用 String.format / formatted / 文本块 + 自己拼接。
比较
String a = "hello";
String b = "hello";
String c = new String("hello");
a == b // true(字符串字面量池化,同一对象)
a == c // false(c 是新对象)
a.equals(c) // true(值相等)
// 忽略大小写
a.equalsIgnoreCase("HELLO"); // true
// 字典序
"a".compareTo("b"); // < 0
永远用 equals() 比 String——== 在字面量上恰好工作,但 user input / 拼接 / new String 都会失效。
常用方法
"hello".length(); // 5
"hello".charAt(1); // 'e'
"hello".substring(1, 4); // "ell"([start, end))
"hello".substring(2); // "llo"
"hello".indexOf("l"); // 2
"hello".lastIndexOf("l"); // 3
"hello".contains("ll"); // true
"hello".startsWith("he"); // true
"hello".endsWith("lo"); // true
"hello".replace("l", "L"); // "heLLo"
"hello".replaceAll("[aeiou]", "*"); // "h*ll*"(正则)
" hi ".trim(); // "hi"
" hi ".strip(); // "hi"(Java 11+,识别 Unicode 空白)
"hello".toUpperCase();
"hello".toLowerCase();
"a,b,c".split(","); // ["a", "b", "c"]
"hello".isEmpty(); // false
"hello".isBlank(); // false(Java 11+,空白也算)
字符遍历
String s = "hello";
// 一种:char 数组
for (char c : s.toCharArray()) {
System.out.println(c);
}
// 二种:codePoints(处理 Unicode 完整字符)
s.codePoints().forEach(cp -> System.out.println((char) cp));
// 三种:传统索引
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
}
中文 / emoji 注意:length() 返回code unit数(UTF-16),不是字符数。'😀'.length() 是 2。
字符串 → 字节
String s = "你好";
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
String back = new String(bytes, StandardCharsets.UTF_8);
永远指定编码——别依赖 getBytes() 默认行为(平台相关,BAD)。
StringBuilder vs StringBuffer
| 线程安全 | 速度 | |
|---|---|---|
| StringBuilder | ❌ | 快 |
| StringBuffer | ✅(synchronized) | 慢 |
99% 场景用 StringBuilder——单线程不需要 buffer 的锁开销。
实战清单
- 循环 / 多步拼接 → StringBuilder
- 多行字符串 → 文本块
"""...""" - 格式化 →
"%s".formatted(...)或String.format - 比较 →
equals(),永远不用== - 编码 → 显式指定
StandardCharsets.UTF_8
→ 下一篇 控制流