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

→ 下一篇 控制流