Java String 类及相关工具
Java String 类及相关工具
在 Java 中,String 类是最常用的类之一,用于处理文本数据等。此外,Java 还提供了 StringBuffer、StringBuilder、StringJoiner 等工具类,以满足不同场景下的字符串操作需求。
String 类
String 类的核心特性
- 不可变性:
String对象一旦创建,其内容(底层存储的字符序列)无法修改。任何看似“修改”字符串的操作(如拼接、替换),本质都是创建新的String对象。 - 底层存储:
String底层基于char[]数组存储字符序列(Java 9 及以上优化为byte[],根据编码自适应存储,节省内存)。 - 引用存储:
String变量存储的是对String对象的引用(内存地址),而非字符串本身;String对象存储在堆内存或字符串常量池中。
String 对象的创建方式
String 对象有三种常见创建方式,分别适用于不同场景:
方式1:通过字符串直接量创建
Java 将字符串直接量视为 String 对象,可直接赋值给 String 变量,该对象会优先存储在字符串常量池(提高复用性,节约内存)。
// 直接量创建:对象存储在字符串常量池
String message = "welcome to java";
方式2:通过 new 关键字创建
通过 new String(...) 创建对象时,会先在堆内存中创建 String 对象。
// new 关键字创建:堆内存中创建对象,常量池可能同步创建副本
String message1 = new String("welcome to java");
方式3:通过字符数组创建
将字符数组转换为 String 对象,适用于从字符序列构建字符串的场景。
// 字符数组创建
char[] charArray = {'g', 'o', 'o', 'd'};
String message2 = new String(charArray); // 结果:"good"
// 截取字符数组的部分内容创建(参数:字符数组、起始索引、长度)
String message3 = new String(charArray, 1, 2); // 从索引1开始,取2个字符,结果:"oo"
综合示例代码
package com.inherit;
/**
* @author Jing61
*/
public class StringTest {
public static void main(String[] args) {
// 方式1:字符串直接量
String s1 = "Welcome to java!"; // 结果:"Welcome to java!"
// 方式2:字符数组(完整数组)
char[] letters = {'H', 'e', 'l', 'l', 'o'};
String s2 = new String(letters); // 结果:"Hello"
// 方式3:字符数组(部分截取)
String s3 = new String(letters, 1, 3); // 从索引1取3个字符,结果:"ell"
}
}
不可变字符串与字符串常量池
不可变性的本质
String 的不可变性源于底层 char[] 数组被 private final 修饰:
private:外部无法直接访问数组;final:数组引用无法指向新的数组,且数组长度固定。
示例:看似“修改”实则创建新对象
String s = "java"; // 创建对象1:内容为"java",s指向该对象
s = "HTML"; // 创建对象2:内容为"HTML",s指向新对象
// 此时对象1仍存在于内存中,但无引用指向,后续会被垃圾回收
字符串常量池(String Constant Pool)
为提高效率、节约内存,Java 虚拟机(JVM)维护一个字符串常量池,存储具有相同字符序列的 String 对象(即“限定字符串”),确保相同直接量仅创建一个对象。
常量池的关键特性
- 直接量自动入池:通过字符串直接量创建的对象,会自动加入常量池(若池中已存在相同序列,则直接复用)。
new对象需手动入池:通过new创建的对象默认在堆内存,需调用intern()方法手动加入常量池(若池中无相同序列,则将对象引用存入;若已有,则返回池中的引用)。
常量池操作示例与分析
示例1:直接量与 new 对象的对比
String s1 = "a"; // 常量池创建"a",s1指向池对象
String s2 = "b"; // 常量池创建"b",s2指向池对象
String s3 = "a" + "b"; // 编译器优化为"ab",常量池创建"ab",s3指向池对象
String s4 = s1 + s2; // s1、s2是引用变量,编译器不优化,堆内存new String("ab"),s4指向堆对象
String s5 = "ab"; // 常量池已存在"ab",s5指向池对象
内存示意图

输出结果
System.out.println(s3 == s4); // false(s3指向池对象,s4指向堆对象)
System.out.println(s3 == s5); // true(均指向池中的"ab")
示例2:intern() 调用顺序的影响
// 情况1:先创建new对象,再调用intern(),最后直接量创建
String x2 = new String("c") + new String("d"); // 堆内存new String("cd"),x2指向堆对象
String x1 = "cd"; // 常量池创建"cd",x1指向池对象
x2.intern(); // 池已有"cd",无操作
System.out.println(x1 == x2); // false(x1指向池,x2指向堆)

// 情况2:先调用intern(),再直接量创建
String x2 = new String("c") + new String("d"); // 堆内存new String("cd"),x2指向堆对象
x2.intern(); // 池无"cd",将x2的引用存入池
String x1 = "cd"; // 池已有x2的引用,x1指向x2(堆对象)
System.out.println(x1 == x2); // true(x1、x2均指向堆中的"cd")

String 类常用方法
String 类提供大量方法用于字符串操作,可分为基本属性、拼接与转换、比较、截取、查找、替换与分割等类别,以下为核心方法详解。
基本属性与转换
public int length():获取字符串当中含有的字符个数,返回字符串长度。
public String concat(String str):将当前字符串和参数字符串str连接,返回值新的字符串。
public char charAt(int index):获取指定索引位置的单个字符。(索引从0开始。)
public String toUpperCase():返回所有字母大写的新字符串
public String toLowerCase():返回所有字母小写的新字符串
public String trim(): 返回去掉两边空白字符的新字符串。
//字符串比较
public boolean equals(String str):如果该字符串等于字符串str,返回true
public boolean equalsIgnoreCase(String str):如果该字符串等于字符串str,返回true.不区分大小写
public int compareTo(String str):返回一个大于0、等于0、小于0的整数,表明一个字符串是否大于、等于或者小于str
public int compareToIgnoreCase(String str):返回一个大于0、等于0、小于0的整数,表明一个字符串是否大于、等于或者小于str。不区分大小写
public boolean startsWith(String prefix): 返回字符串是否以前缀prefix开头
public boolean endsWith(String suffix): 返回字符串是否以后缀suffix结束
public boolean contains(String str): 检查字符串中是否包含子串str
//字符串截取
public String substring(int begin):截取字符串,从特定位置begin的字符开始到字符串结尾。
public String substring(int begin,int end):截取字符串,从特定位置begin的字符开始到end-1的字符。(长度为end - begin)
//字符串查找,提供了几个版本的indexOf和lastIndexOf方法
public int indexOf(String str):查找参数字符串在本字符串当中首次出现str的索引位置,如果没有返回-1值。
public int indexOf(String str,int fromIndex):查找参数字符串在本字符串当中fromIndex之后首次出现str的索引位置,如果没有返回-1值。
public int lastIndexOf(String str):查找参数字符串在本字符串当中最后一个出现str的索引位置,如果没有返回-1值。
public int lastIndexOf(String str,int fromIndex):查找参数字符串在本字符串当中fromIndex之前出现最后一个str的索引位置,如果没有返回-1值。
字符串替换与分割
替换方法
| 方法签名 | 功能描述 |
|---|---|
String replace(String oldStr, String newStr) |
替换所有匹配的 oldStr 为 newStr(不支持正则,仅精确匹配) |
String replaceFirst(String regex, String newStr) |
替换第一个匹配正则 regex 的子串为 newStr |
String replaceAll(String regex, String newStr) |
替换所有匹配正则 regex 的子串为 newStr(支持正则表达式) |
分割方法
| 方法签名 | 功能描述 |
|---|---|
String[] split(String regex) |
按正则 regex 分割字符串,返回分割后的字符串数组;若末尾有分隔符,会忽略空串 |
示例代码
package com.inherit;
/**
* @author Jing61
*/
public class StringTest {
public static void main(String[] args) {
String token = "我认为他是一个好老师,你觉得他是吗?";
//将所有的"他"替换为"him"
String info = token.replace("他", "him");
System.out.println(info);
//替换第一个找到的"他"
System.out.println(token.replaceFirst("他", "him"));
//替换全部,支持模式匹配(正则表达式)
System.out.println(token.replaceAll("他", "him"));
//分割
String[] tokens = "Linux@Java@Spring".split("@");
for(String t : tokens)
System.out.println(t);
}
}
模式匹配(正则表达式)
String 类的 matches() 方法支持正则表达式匹配,用于验证字符串格式(如手机号、邮箱等)。
示例:验证手机号格式
package com.inherit;
/**
* @author Jing61
*/
public class StringTest {
public static void main(String[] args) {
String reg = "^1[358]\\d{9}$"; // 该正则表达式为1开头,第二位为3、5、8,共2 + 9位
System.out.println("110".matches(reg)); // 输出为false
System.out.println("1300".matches(reg));// 输出为false
System.out.println("17300001111".matches(reg));// 输出为false
System.out.println("15300001121".matches(reg));// 输出为true
}
}
正则表达式详解:可参考 菜鸟教程 - Java 正则表达式。
字符串与数组之间的转换
//字符串和字符数组进行转换
char[] letters = token.toCharArray();
System.out.println(letters.length);
new String(letters);
System.out.println(new String(letters,0,3));
字符串与数值之间的转换
可以使用Double.parseDouble(str)或者Integer.parseInt(str)将一个字符串转为一个double或者int值,也可以使用字符串的连接操作符将字符或者数值转换为字符串。另外一种将数字转为字符串的方法是使用重载的静态valueOf方法。
StringBuffer 与 StringBuilder
String 的不可变性导致频繁修改字符串时(如循环拼接)会创建大量临时对象,效率低下。StringBuffer 和 StringBuilder 作为可变字符串类,可直接修改内部字符序列,避免创建新对象,提高效率。
核心区别与适用场景
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 线程安全性 | 线程安全(方法被 synchronized 修饰) |
线程不安全(无同步修饰) |
| 效率 | 较低(同步开销) | 较高(无同步开销) |
| 适用场景 | 多线程环境(如服务器端并发操作) | 单线程环境(如客户端、单线程业务逻辑) |
常用方法(以 StringBuilder 为例)
| 方法签名 | 功能描述 |
|---|---|
StringBuilder append(任意类型) |
追加内容到字符串末尾,返回对象本身(支持链式调用) |
StringBuilder insert(int index, 任意类型) |
在索引 index 处插入内容,返回对象本身 |
StringBuilder delete(int start, int end) |
删除索引 start 至 end-1 的内容,返回对象本身 |
StringBuilder reverse() |
反转字符串内容,返回对象本身 |
String toString() |
将 StringBuilder 转换为 String 对象 |
代码示例
StringBuilder 示例
package com.inherit;
/**
* @author Jing61
*/
public class StringBuilderTest {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
// 链式追加:append返回对象本身,可连续调用
sb.append("hello").append(" ").append("world");
System.out.println(sb); // 输出:"hello world"
// 插入内容:在索引5处插入":"
sb.insert(5, ":");
System.out.println(sb); // 输出:"hello: world"
// 删除内容:删除索引5~6(左闭右开)的字符(即":")
sb.delete(5, 6);
System.out.println(sb); // 输出:"hello world"
// 反转字符串
sb.reverse();
System.out.println(sb); // 输出:"dlrow olleh"
// 转换为String
String result = sb.toString();
System.out.println(result); // 输出:"dlrow olleh"
}
}
StringBuffer 示例
package com.inherit;
/**
* @author Jing61
*/
public class StringBufferTest {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
// 追加内容
sb.append("hello").append(" ").append("world");
System.out.println(sb); // 输出:"hello world"
// 插入内容
sb.insert(5, ":");
System.out.println(sb); // 输出:"hello: world"
// 删除内容
sb.delete(5, 6);
System.out.println(sb); // 输出:"hello world"
}
}
实战:判断回文字符串(忽略非字母数字)
package com.inherit;
/**
* 判断回文字符串时忽略既非字母又非数字的字符
* 1、过滤出原始字符串中的字母,组成一个新的字符串
* 2、将新的字符串进行反转既非字母又非数字
* 3、将反转后的字符串和过滤后字符串进行比较,如果相等则为回文串
* @author Jing61
*/
public class Plalindrome {
public static String filter(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isLetterOrDigit(c)) sb.append(c);
}
return sb.toString(); // new String()
}
public static String reverse(String s) {
StringBuilder sb = new StringBuilder(s);
return sb.reverse().toString();
}
public static boolean isPalindrome(String s) {
return filter(s).equals(reverse(filter(s)));
}
public static void main(String[] args) {
System.out.println(isPalindrome("A man, a plan, a canal: Panama")); // false
System.out.println(isPalindrome("race a car"));// false
System.out.println(isPalindrome(" "));// true
System.out.println(isPalindrome("Ava 232 avA"));// true
}
}
StringJoiner 与 String.join()
在需要“分隔符拼接数组/集合”的场景(如将 {"a", "b", "c"} 拼接为 "[a, b, c]"),StringJoiner 和 String.join() 可简化代码,避免手动处理多余分隔符。
StringJoiner
StringJoiner 支持指定分隔符、前缀、后缀,自动处理分隔符拼接,无需手动删除末尾多余字符。
构造器与核心方法
| 构造器/方法 | 功能描述 |
|---|---|
StringJoiner(CharSequence delimiter) |
仅指定分隔符(前缀、后缀默认为空) |
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) |
指定分隔符、前缀、后缀 |
StringJoiner add(CharSequence str) |
向拼接器中添加字符串,返回对象本身(支持链式调用) |
String toString() |
返回拼接后的完整字符串 |
代码示例
package com.inherit;
import java.util.StringJoiner;
/**
* @author Jing61
*/
public class StringJoinerTest {
public static void main(String[] args) {
String[] names = {"Jing61", "Jing6", "Jing1"};
System.out.println(toString(names));
}
public static String toString(String[] names) {
StringJoiner sj = new StringJoiner(",", "[", "]");// 创建StringJoiner对象, 分割符, 前缀, 后缀
for (String name : names) sj.add(name);
return sj.toString();
}
}
String.join()
String.join() 是 String 类的静态方法,内部基于 StringJoiner 实现,适用于无需前缀/后缀的简单拼接场景,代码更简洁。
代码示例
public static void printArray(String[] names) {
System.out.println(String.join(", ",names));
}
总结
| 类/方法 | 核心特性 | 适用场景 |
|---|---|---|
String |
不可变、线程安全、常量池复用 | 字符串内容固定,无需频繁修改 |
StringBuilder |
可变、线程不安全、效率高 | 单线程环境下频繁修改字符串 |
StringBuffer |
可变、线程安全、效率较低 | 多线程环境下频繁修改字符串 |
StringJoiner |
支持分隔符、前缀、后缀,自动处理拼接 | 复杂格式的数组/集合拼接(如 "[a, b, c]") |
String.join() |
简化无前后缀的拼接,代码简洁 | 简单的数组/集合拼接(如 "a, b, c") |

浙公网安备 33010602011771号