杰瑞科技汇

Java中String的本质是什么?

String 是 Java 编程中最基础、最重要的类之一,理解它对于掌握 Java 至关重要。

Java中String的本质是什么?-图1
(图片来源网络,侵删)

核心概念:String 是什么?

String(字符串)是一个用于表示文本数据的对象,它是一个字符序列,"Hello, World!"

在 Java 中,String 被声明为 final 类,这意味着:

  1. 不可变性:一旦一个 String 对象被创建,它的内容就不能被改变,任何看似修改字符串的操作(如 concat, replace),实际上都是创建了一个新的 String 对象,而原始对象保持不变。
  2. 不能被继承:你无法创建一个 String 的子类。

String 的不可变性

这是 String 最核心、最重要的特性,理解它至关重要。

什么是不可变性?

想象一下你有一个字符串变量:

Java中String的本质是什么?-图2
(图片来源网络,侵删)
String str = "hello";

内存中会创建一个对象,内容是 "hello"。

你执行一个修改操作:

str = str + " world";

这行代码不是在原有的 "hello" 后面追加 " world",它的实际过程是:

  1. 在内存中创建一个新的字符串对象,内容为 "hello world"。
  2. 让变量 str 指向这个新的对象。
  3. 原来的 "hello" 对象,如果没有其他引用指向它,就会被垃圾回收器回收。

String 对象的内容是恒定的,就像一个用 final 修饰的字符数组

为什么设计成不可变?

不可变性带来了很多好处:

  • 线程安全:因为字符串内容不会改变,所以在多线程环境下,多个线程可以安全地共享同一个字符串对象,而无需额外的同步措施,这大大减少了并发编程的复杂性。
  • 安全性:字符串被广泛用于构建网络连接、文件路径、数据库查询等,如果字符串是可变的,恶意代码可能会在你不知情的情况下修改这些关键信息,导致严重的安全漏洞(修改 URL 或 SQL 语句)。
  • 性能优化(字符串常量池):这是 Java 的一大设计亮点,Java 维护一个特殊的内存区域,叫做字符串常量池
    • 当你使用字面量(如 String s = "abc";)创建字符串时,JVM 会先检查常量池中是否已经存在 "abc" 这个字符串。
    • 如果存在,就直接返回该对象的引用,避免重复创建相同的对象,节省内存。
    • 如果不存在,就在常量池中创建一个新的,并返回引用。
    • 不可变性是字符串常量池能够正常工作的前提,如果字符串内容可以被改变,那么一个引用的改变就会影响到所有指向常量池中同一个字符串的其他引用,这将是灾难性的。

如何创建 String 对象?

主要有两种方式:

使用字面量(最常用)

String str1 = "Hello";
String str2 = "Hello"; // str1 和 str2 指向常量池中的同一个对象

使用 new 关键字

String str3 = new String("Hello"); // str3 指向堆内存中的一个新的对象
String str4 = new String("Hello"); // str4 指向堆内存中另一个新的对象

区别

  • 字面量:创建的字符串会被放入常量池,如果内容相同,则共享对象。
  • new:创建的字符串对象会被放入堆内存,即使内容相同,也会创建多个不同的对象。

常用方法

String 类提供了非常丰富的方法来操作字符串。

方法 描述 示例
length() 返回字符串的长度。 "abc".length() -> 3
charAt(int index) 返回指定索引处的字符。 "abc".charAt(1) -> 'b'
substring(int beginIndex) 截取从 beginIndex 开始到结尾的子串。 "abcdef".substring(2) -> "cdef"
substring(int begin, int end) 截取从 beginIndexendIndex-1 的子串。 "abcdef".substring(2, 4) -> "cd"
indexOf(String str) 返回子字符串第一次出现的索引。 "hello world".indexOf("world") -> 6
toLowerCase() / toUpperCase() 转换为小写/大写。 "Hello".toLowerCase() -> "hello"
trim() 去除字符串两端的空白字符。 " hello ".trim() -> "hello"
replace(char old, char new) 替换所有字符。 "hello".replace('l', 'p') -> "heppo"
split(String regex) 根据正则表达式分割字符串,返回字符串数组。 "a,b,c".split(",") -> ["a", "b", "c"]
equals(Object obj) 比较两个字符串的内容是否相等(区分大小写)。 "abc".equals("ABC") -> false
equalsIgnoreCase(String another) 比较两个字符串的内容是否相等(不区分大小写)。 "abc".equalsIgnoreCase("ABC") -> true
concat(String str) 连接字符串。 "a" + "b" -> "ab"
"a".concat("b") -> "ab"

StringStringBuilderStringBuffer 的区别

在处理大量字符串拼接时,由于 String 的不可变性,频繁地 操作会创建大量临时对象,严重影响性能,这时就需要 StringBuilderStringBuffer

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 线程安全(本身不变) 非线程安全 线程安全
性能 频繁修改时性能差 性能最好 性能比 StringBuilder 稍差(因为有同步开销)
使用场景 存储和操作少量、固定的文本 单线程环境下进行大量字符串修改 多线程环境下进行大量字符串修改

代码示例:性能对比

// String 拼接 (性能差)
String str = "";
for (int i = 0; i < 10000; i++) {
    str = str + "a"; // 每次循环都创建新对象
}
// StringBuilder 拼接 (性能好)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append("a"); // 在原有对象上修改,不创建新对象
}
String result = sb.toString();

  1. 本质String 是一个 final 类,代表一个不可变的字符序列。
  2. 不可变性:是其核心特性,带来了线程安全、高安全性和性能优化(字符串常量池)等好处。
  3. 创建:可通过字面量(放入常量池)或 new 关键字(放入堆)创建。
  4. 操作:提供了丰富的方法进行查找、截取、替换、比较等。
  5. 性能:在需要频繁修改字符串的场景下,应优先使用 StringBuilder(单线程)或 StringBuffer(多线程),而不是直接使用 String 的 操作。

掌握 String 的这些特性,是编写高效、健壮的 Java 代码的基石。

分享:
扫描分享到社交APP
上一篇
下一篇