String 不可变性与字符串池
理解 String 为何不可变、字符串池如何工作,以及何时使用 == 与 equals
String 不可变性与字符串池 是 CoddyKit 上的免费 Java Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
String 的不可变性与字符串池
Java 中的 String 是不可变的。理解不可变性、字符串池,以及引用相等性与值相等性之间的区别,有助于避免隐蔽的错误。
String 为什么是不可变的
String 对象在创建后无法修改。任何看似“更改” String 的操作,实际上都会创建一个新的 String 对象。这使得对象可以共享,并有助于保证线程安全,还能缓存 hashCode。
String s = "hello";
s.toUpperCase(); // creates a new String, does NOT modify s
System.out.println(s); // still "hello"
String upper = s.toUpperCase(); // must capture the result
System.out.println(upper); // HELLO字符串池(驻留)
字符串字面量会存储在字符串池中(堆中的一个特殊区域)。内容相同的两个字面量会共享同一个对象。
String a = "hello"; // goes into pool
String b = "hello"; // same object from pool
String c = new String("hello"); // forces new object outside pool
System.out.println(a == b); // true (same pool reference)
System.out.println(a == c); // false (different objects)
System.out.println(a.equals(c)); // true (same content)== 与 equals()
比较 String 的内容时,请始终使用 .equals()。== 运算符检查两个变量是否指向同一个对象(引用相等性)。
String input = new String("admin"); // from user input, not pooled
String role = "admin";
// Dangerous: fails because different objects
if (input == role) System.out.println("Same"); // not printed!
// Correct: compares content
if (input.equals(role)) System.out.println("Equal!"); // prints
// Null-safe: put constant first to avoid NPE
if ("admin".equals(input)) System.out.println("Admin"); // printsintern():手动将 String 放入字符串池
String.intern() 会强制将一个 String 放入字符串池,并返回池中的引用。在现代 Java 中通常很少需要使用它。
String s1 = new String("interned");
String s2 = s1.intern();
String s3 = "interned"; // already in pool
System.out.println(s2 == s3); // true (both point to pool)
System.out.println(s1 == s3); // false (s1 is off-pool)
// Modern use: prefer String.intern() only when you need
// identity-based caching with thousands of repeated stringsString 拼接的行为
使用 + 进行拼接会创建新的 String 对象。在循环中执行此操作的复杂度为 O(n²)。编译器会优化单个表达式,但不会优化基于循环的拼接。
// Compiler optimizes this to a StringBuilder automatically:
String result = "Hello, " + "World" + "!";
// In loops: compiler does NOT optimize — use StringBuilder explicitly!
List<String> words = List.of("a", "b", "c", "d");
String bad = ""; // O(n^2) — new object each iteration
for (String w : words) bad += w;
StringBuilder sb = new StringBuilder();
for (String w : words) sb.append(w); // O(n) — efficient
String good = sb.toString();
System.out.println(good); // abcd正确比较 String
请始终使用 equals();不区分大小写的比较请使用 equalsIgnoreCase()。使用 compareTo() 进行字典序排序。
String a = "Java";
String b = "java";
System.out.println(a.equals(b)); // false
System.out.println(a.equalsIgnoreCase(b)); // true
System.out.println(a.compareToIgnoreCase(b)); // 0 (equal)
System.out.println(a.compareTo(b)); // negative (J < j in Unicode)String 的哈希码缓存
由于字符串是不可变的,Java 会在第一次调用 hashCode 后将其缓存起来。后续调用会直接返回缓存值,因此 String 作为 HashMap 的键时非常高效。
String key = "product:12345";
// hashCode computed once and cached:
int h1 = key.hashCode();
int h2 = key.hashCode(); // returns cached value
System.out.println(h1 == h2); // true
// Strings are ideal HashMap keys because:
// 1. hashCode is consistent (immutable content)
// 2. hashCode is cached (fast repeated lookups)
// 3. equals is well-defined (content-based)
Map<String, Integer> map = new HashMap<>();
map.put("key", 42);
System.out.println(map.get("key")); // 42String 是 final 类
String 类是 final 类,无法被继承。这确保任何子类都无法破坏不可变性或 hashCode 的一致性。
// String is final — this will not compile:
// class MutableString extends String {} // compile error
// You cannot override String behavior.
// If you need custom string-like behavior, use a wrapper:
record ProductSku(String value) {
ProductSku { if (!value.matches("[A-Z]{3}-[0-9]{4}")) throw new IllegalArgumentException(); }
@Override public String toString() { return value; }
}String.format 与拼接
对于包含多个部分的字符串,为了提高可读性并便于本地化,请优先使用 String.format() 或文本块,而不是字符串拼接。
String name = "Alice";
int age = 30;
double score = 97.5;
// Concatenation — harder to read with many parts
String bad = "User: " + name + ", Age: " + age + ", Score: " + score;
// String.format — clear placeholders
String good = String.format("User: %s, Age: %d, Score: %.1f", name, age, score);
System.out.println(good);
// User: Alice, Age: 30, Score: 97.5空值安全的 String 操作
字符串为 null 时,许多 String 操作会抛出 NPE。请使用防御式写法或 String.valueOf() 来安全处理 null。
String value = null;
// Null-safe length check
int len = (value != null) ? value.length() : 0;
System.out.println(len); // 0
// Null-safe comparison (put constant first)
boolean isAdmin = "admin".equals(value); // false, no NPE
// String.valueOf converts null to "null" string
System.out.println(String.valueOf(value)); // "null"
System.out.println(Objects.toString(value, "default")); // "default"快速检查
下面的代码会打印什么?
String a = "hello";
String b = new String("hello");
System.out.println(a.equals(b));
System.out.println(a == b);回顾:String 的不可变性与字符串池
要点:
- String 是不可变的:操作会返回新的 String 对象,而不会修改原对象
- 字符串字面量会被放入字符串池:内容相同的两个字面量会共享一个对象
- 比较 String 内容时,请始终使用 .equals();== 比较的是对象引用
- 不区分大小写的比较请使用 equalsIgnoreCase()
- 循环中的字符串拼接复杂度为 O(n²),请使用 StringBuilder 提高效率
- String 会缓存 hashCode,因此作为 HashMap 的键时高效且安全
常见问题解答
「String 不可变性与字符串池」课时是免费的吗?
是的 — 「String 不可变性与字符串池」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「String 不可变性与字符串池」这节课中我会学到什么?
理解 String 为何不可变、字符串池如何工作,以及何时使用 == 与 equals 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「String 不可变性与字符串池」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。