在Java编程中,String类可能是我们打交道最多的类之一。每次看到双引号包裹的文本,背后其实都是String类的实例在默默工作。但你真的了解这个看似简单的类吗?我刚开始接触Java时,曾天真地认为String就是"一段文字"而已,直到后来在项目里踩了无数坑,才明白这个类的精妙之处。
String类在java.lang包中,这意味着我们不需要额外导入就能直接使用。它被设计为final类,所以不能被继承。更特别的是,String对象一旦创建就不可改变(immutable)——这个特性经常让新手困惑。比如当你调用toUpperCase()方法时,并不是修改原字符串,而是返回一个全新的String对象。
重要提示:由于String的不可变性,在循环中频繁拼接字符串会导致大量临时对象产生,这时应该改用StringBuilder。
创建String对象至少有三种常见方式:
java复制// 字面量方式(推荐)
String s1 = "Hello World";
// 构造器方式
String s2 = new String("Hello World");
// 字符数组转换
char[] chars = {'H','e','l','l','o'};
String s3 = new String(chars);
字面量方式会检查字符串常量池,如果已有相同内容则直接引用,否则新建。而用new则强制创建新对象,即使内容相同。这直接影响了内存使用和比较结果:
java复制System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
这几个方法在业务代码中出现频率极高:
length():返回字符串长度(注意不是length属性)isEmpty():判断是否为空字符串(length为0)charAt(int index):获取指定位置的字符使用时有个细节要注意:charAt的索引从0开始,如果超出范围会抛出StringIndexOutOfBoundsException。我见过不少新手在处理用户输入时忽略了这个检查:
java复制String input = getUserInput();
if(input != null && !input.isEmpty()) {
char firstChar = input.charAt(0); // 危险!可能抛异常
}
更安全的写法应该先检查长度:
java复制if(input != null && input.length() > 0) {
char firstChar = input.charAt(0);
}
equals()和equalsIgnoreCase()是最常用的比较方法:
java复制String a = "Java";
String b = "java";
System.out.println(a.equals(b)); // false
System.out.println(a.equalsIgnoreCase(b)); // true
实际经验:在比较用户输入或配置值时,通常应该先trim()再比较,避免因首尾空格导致意外结果。
compareTo()系列方法用于排序场景,返回的是字典序差值:
java复制"apple".compareTo("banana"); // 负数,因为a在b前面
"Java".compareTo("java"); // -32,因为J和j的ASCII码差32
这几个方法能帮你快速定位内容:
contains(CharSequence s):是否包含子串indexOf()/lastIndexOf():子串首次/末次出现位置startsWith()/endsWith():检查前缀/后缀一个实用技巧:indexOf可以用于提取特定标记之间的内容:
java复制String url = "https://example.com/path?param=value";
int queryStart = url.indexOf("?");
if(queryStart > 0) {
String query = url.substring(queryStart + 1);
System.out.println(query); // 输出param=value
}
substring()可能是最常用的字符串方法之一,它有两个重载版本:
java复制String str = "Hello World";
str.substring(6); // "World"(从索引6到末尾)
str.substring(0, 5); // "Hello"(索引0到5-1)
常见陷阱:第二个参数是结束索引(exclusive),不是长度。很多人会误写成substring(0, length)导致越界。
虽然可以用+号拼接字符串,但在循环中更高效的方式是:
java复制// 低效做法(每次循环都创建新String对象)
String result = "";
for(int i=0; i<100; i++) {
result += i;
}
// 高效做法
StringBuilder builder = new StringBuilder();
for(int i=0; i<100; i++) {
builder.append(i);
}
String result = builder.toString();
Java 11新增的repeat()方法可以方便地重复字符串:
java复制"-".repeat(10); // "----------"
toUpperCase()和toLowerCase()看似简单,但在国际化场景下有个坑:
java复制// 土耳其语环境下会出问题
"TITLE".toLowerCase(); // 期望"title",但土耳其语中I的小写是ı
// 安全做法是指定Locale
"TITLE".toLowerCase(Locale.ENGLISH);
format()方法类似C语言的printf:
java复制String msg = String.format("欢迎%s!您是第%d位访客", "张三", 100);
Java 15引入的文本块(Text Blocks)让多行字符串更清晰:
java复制String html = """
<html>
<body>
<p>Hello World</p>
</body>
</html>
""";
matches(String regex):判断是否匹配正则split(String regex):按正则分割字符串replaceAll()/replaceFirst():正则替换处理CSV文件时,split经常派上用场:
java复制String line = "a,b,c,d";
String[] parts = line.split(","); // ["a","b","c","d"]
但要注意特殊字符需要转义:
java复制"a.b.c".split("."); // 错误!需要转义
"a.b.c".split("\\."); // 正确
trim():去除首尾空白(仅ASCII空格)strip():Java 11新增,能处理全角空格等Unicode空白java复制" hello ".trim(); // "hello"
" hello ".strip(); // "hello"(全角空格被移除)
valueOf()是个很实用的静态方法,能把各种类型转为字符串:
java复制String.valueOf(123); // "123"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 调用对象的toString()
toCharArray()和getBytes()常用于底层操作:
java复制char[] chars = "hello".toCharArray();
byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8);
JVM有个字符串常量池,能复用相同内容的字符串:
java复制String a = "java";
String b = "java";
System.out.println(a == b); // true,指向同一对象
但通过new创建的字符串不会复用:
java复制String c = new String("java");
System.out.println(a == c); // false
现代Java编译器会把简单的+号拼接优化为StringBuilder,但复杂场景仍需手动控制:
java复制// 编译器会优化为StringBuilder
String result = str1 + str2 + str3;
// 循环中仍需手动优化
StringBuilder builder = new StringBuilder();
for(String str : list) {
builder.append(str);
}
处理多语言文本时要注意:
codePoint相关方法处理辅助平面字符java复制// 错误示范
"𝄞".length(); // 返回2,因为需要两个char表示
// 正确做法
"𝄞".codePointCount(0, "𝄞".length()); // 返回1
任何字符串操作前都应检查null:
java复制// 危险操作
str.trim().toUpperCase();
// 安全做法
String result = str == null ? null : str.trim().toUpperCase();
乱码通常源于编码不一致:
java复制// 错误示范
new String(bytes); // 使用平台默认编码
// 正确做法
new String(bytes, StandardCharsets.UTF_8);
不当使用substring可能导致内存泄漏(Java 7前):
java复制String bigString = "...非常长的字符串...";
String smallPart = bigString.substring(0,10);
// 在Java 6中,smallPart仍会引用bigString的char[]
Java 7+已修复此问题,但类似的引用问题仍需注意。
join()方法简化了字符串拼接:
java复制String.join(", ", "a", "b", "c"); // "a, b, c"
isBlank()比isEmpty()更智能:
java复制" ".isEmpty(); // false
" ".isBlank(); // true
lines()方法方便处理多行文本:
java复制"line1\nline2".lines().count(); // 2
transform()方法允许链式操作:
java复制String result = "hello"
.transform(String::toUpperCase)
.transform(s -> s.repeat(2));
// "HELLOHELLO"
典型的用户名验证流程:
java复制public boolean isValidUsername(String input) {
return input != null
&& !input.isBlank()
&& input.length() >= 4
&& input.length() <= 16
&& input.matches("[a-zA-Z0-9_]+");
}
使用format规范日志格式:
java复制String log = String.format("[%s] %s - %s",
LocalDateTime.now(),
Thread.currentThread().getName(),
message);
银行卡号掩码处理:
java复制public static String maskCardNumber(String cardNumber) {
if(cardNumber == null || cardNumber.length() < 8) {
return cardNumber;
}
int keep = 4;
return cardNumber.substring(0, keep)
+ "*".repeat(cardNumber.length() - keep * 2)
+ cardNumber.substring(cardNumber.length() - keep);
}
在多年的Java开发中,我发现String类的方法虽然基础,但用得好能极大提升代码质量和性能。特别是在处理用户输入、文本解析等场景时,选择合适的方法组合往往能让代码更简洁高效。建议新手不仅要记住方法签名,更要理解每个方法背后的设计意图和使用场景。