0Pricing
Java Academy · 课时

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"); // prints

intern():手动将 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 strings

String 拼接的行为

使用 + 进行拼接会创建新的 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")); // 42

String 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. String 不可变性与字符串池
  2. 使用 StringBuilder 高效拼接
  3. 文本块与 String.format
  4. 使用 Pattern 和 Matcher 处理正则表达式
← 返回 Java Academy