String 常用 API 与字符串综合处理 | JavaSE

String 常用 API 与字符串综合处理

一、学习目标

完成本章后,你应该能够:

  1. 熟练使用 length()charAt()toCharArray() 获取和遍历字符串数据。
  2. 熟练使用 equals()equalsIgnoreCase() 完成字符串内容比较。
  3. 熟练使用 substring() 完成字符串截取,并准确理解“包前不包后”的索引规则。
  4. 使用 replace()contains()startsWith()split() 完成常见字符串处理。
  5. 能够根据业务需求组合多个 String API,解决手机号脱敏、登录校验、文本分割、字符统计等问题。
  6. 能够 Debug 常见的字符串索引越界、== 比较错误、split() 正则表达式误用等问题。

二、核心知识

2.1 String API 到底是什么

String 不只是一个用来保存文本的类型。

Java 为 String 提供了大量实例方法,用于完成:

  • 获取字符串长度
  • 根据索引获取字符
  • 字符串比较
  • 字符串截取
  • 字符串替换
  • 内容查找
  • 前缀判断
  • 字符串分割
  • 字符数组转换

例如:

String text = "JavaSE";

int length = text.length();
char first = text.charAt(0);

可以把一个 String 对象理解为:

字符串数据
    +
一整套字符串处理能力

2.2 String 不可变带来的重要规则

上一章已经学习:

String 是不可变字符串。

因此,本章学习 String API 时必须建立一个非常重要的认知:

很多 String 方法不会修改原字符串,而是返回新的 String。

例如:

String text = "Java";

text.replace("Java", "Python");

System.out.println(text);

结果仍然是:

Java

因为:

text.replace("Java", "Python");

只是产生了一个新的字符串结果。

如果需要保存结果,应写成:

text = text.replace("Java", "Python");

或者:

String newText = text.replace("Java", "Python");

所以看到 String API 时,经常应该思考:

这个方法是在读取字符串,还是在根据原字符串生成新的结果?


2.3 String 常用 API 总览

本阶段重点掌握:

| API | 作用 | | --------------------------------- | ---------------------- | | length() | 获取字符串长度 | | charAt(index) | 获取指定索引位置字符 | | toCharArray() | 转换成字符数组 | | equals() | 比较字符串内容 | | equalsIgnoreCase() | 忽略大小写比较内容 | | substring(beginIndex) | 从指定索引截取到末尾 | | substring(beginIndex, endIndex) | 截取指定区间 | | replace(target, replacement) | 替换字符串内容 | | contains(s) | 判断是否包含指定内容 | | startsWith(prefix) | 判断是否以指定内容开头 | | split(regex) | 按规则分割字符串 |

这些 API 不应该单独死记。

更重要的是建立:

需求
 ↓
选择 String API
 ↓
组合 API
 ↓
得到业务结果

三、使用方法

3.1 length():获取字符串长度

基本格式:

字符串.length()

例如:

String name = "JavaSE";

System.out.println(name.length());

输出:

6

一个非常常见的遍历方式就是:

String text = "Java";

for (int i = 0; i < text.length(); i++) {
    System.out.println(text.charAt(i));
}

3.2 charAt():根据索引获取字符

格式:

字符串.charAt(index)

例如:

String text = "Java";

System.out.println(text.charAt(0));
System.out.println(text.charAt(1));
System.out.println(text.charAt(2));
System.out.println(text.charAt(3));

得到:

J
a
v
a

索引仍然遵循 Java 中最重要的规则:

J a v a
0 1 2 3

因此:

text.charAt(4);

会产生索引越界问题。

合法索引范围:

0 ~ length() - 1

所以遍历字符串通常写:

for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
}

注意不是:

i <= text.length()

3.3 toCharArray():字符串转字符数组

如果需要逐个处理字符串中的字符,可以:

String text = "Java";

char[] chars = text.toCharArray();

for (char c : chars) {
    System.out.println(c);
}

效果:

J
a
v
a

于是遍历字符串主要有两种方式。

方式一:

for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
}

方式二:

char[] chars = text.toCharArray();

for (char c : chars) {
    System.out.println(c);
}

如何选择?

如果需要索引:

charAt()

通常更方便。

如果主要针对每个字符进行处理:

toCharArray()

也非常自然。


3.4 equals():比较字符串内容

前面已经学习:

==

比较引用类型时,关注的通常是:

两个变量保存的引用是否相同。

但实际业务中,我们大多数时候需要比较:

两个字符串的内容是不是一样。

应该使用:

equals()

例如:

String correctUsername = "admin";
String inputUsername = "admin";

if (correctUsername.equals(inputUsername)) {
    System.out.println("用户名正确");
}

推荐:

correctUsername.equals(inputUsername)

而不是:

inputUsername.equals(correctUsername)

如果 inputUsername 可能是 null,后者可能产生:

NullPointerException

而已知常量通常不会是 null

例如:

"admin".equals(inputUsername)

也是常见写法。


3.5 equalsIgnoreCase():忽略大小写比较

例如:

String command = "LOGIN";

System.out.println(command.equalsIgnoreCase("login"));

结果:

true

这适用于某些不区分英文大小写的场景,例如:

YES / yes / Yes
EXIT / exit / Exit

例如:

if ("exit".equalsIgnoreCase(command)) {
    System.out.println("程序退出");
}

需要注意:

是否应该忽略大小写取决于业务规则。

例如密码一般不应该随意忽略大小写。


3.6 substring():字符串截取

这是 String 中非常重要的方法。

从某个位置一直截取到结尾

String text = "JavaSE";

String result = text.substring(4);

System.out.println(result);

结果:

SE

可以理解为:

J a v a S E
0 1 2 3 4 5
        ↑
       开始

3.7 substring(beginIndex, endIndex)

格式:

substring(开始索引, 结束索引)

规则:

包含开始索引,不包含结束索引。

即:

[beginIndex, endIndex)

这就是 Java 中经常提到的:

包前不包后。

例如:

String text = "JavaSE";

String result = text.substring(0, 4);

System.out.println(result);

得到:

Java

因为:

索引:

0 1 2 3 4 5
J a v a S E

substring(0, 4)

取:
0、1、2、3

不取:
4

因此截取结果长度可以直接计算:

endIndex - beginIndex

3.8 手机号脱敏案例

假设:

String phone = "18612346520";

希望得到:

186****6520

分析:

186 1234 6520
↑       ↑
保留    保留

前三位:

phone.substring(0, 3)

后四位:

phone.substring(7)

组合:

String phone = "18612346520";

String result =
        phone.substring(0, 3)
        + "****"
        + phone.substring(7);

System.out.println(result);

结果:

186****6520

这个案例非常典型,因为它体现了:

需求分析
→ 确定索引
→ substring
→ 字符串拼接

而不是单纯背 API。


3.9 replace():字符串替换

例如:

String text = "Java is good, Java is powerful";

String result = text.replace("Java", "Python");

System.out.println(result);

会得到一个新的字符串结果。

注意原字符串:

text

本身不会被修改。

因此:

String text = "Java";

text.replace("Java", "Python");

System.out.println(text);

仍然输出:

Java

正确保存结果:

text = text.replace("Java", "Python");

3.10 contains():判断是否包含指定内容

例如:

String title = "JavaSE字符串完整教程";

boolean result = title.contains("JavaSE");

System.out.println(result);

输出:

true

实际开发中常见于:

  • 简单关键词判断
  • 文本过滤
  • 文件名检测
  • 搜索匹配
  • 内容校验

例如:

String fileName = "JavaSE学习笔记.md";

if (fileName.contains("Java")) {
    System.out.println("找到 Java 相关文档");
}

3.11 startsWith():判断字符串开头

例如:

String url = "https://yulanlin.cn";

System.out.println(url.startsWith("https://"));

输出:

true

常见应用:

判断 URL 协议
判断文件名前缀
判断命令格式
判断编号格式

例如:

String studentId = "20260001";

if (studentId.startsWith("2026")) {
    System.out.println("2026级学生");
}

3.12 split():字符串分割

例如:

String data = "Java,Python,C++";

String[] languages = data.split(",");

for (String language : languages) {
    System.out.println(language);
}

结果:

Java
Python
C++

也就是说:

"Java,Python,C++"

          ↓ split(",")

["Java", "Python", "C++"]

split() 的返回值是:

String[]

不是一个 String。


3.13 split() 有一个非常重要的规则

split() 的参数不是普通字符串匹配规则,而是:

正则表达式(Regular Expression)。

例如:

String text = "www.yulanlin.cn";

如果希望按照 . 分割,不能想当然地写:

text.split(".");

因为正则表达式中的:

.

具有特殊含义。

通常应该转义:

String[] parts = text.split("\\.");

这里出现两层规则:

正则中的点:

\.

Java字符串中反斜杠还需要转义:

\\.

最终:

text.split("\\.")

现在暂时只需要记住:

split() 接收正则表达式,不要把所有符号都当普通文本。

完整正则表达式知识可以在后续需要时专门学习。


3.14 String API 综合示例

import java.util.Arrays;

public class StringApiDemo {
    public static void main(String[] args) {
        String text = "JavaSE-String-API";

        System.out.println(text.length());

        System.out.println(text.charAt(0));

        System.out.println(
                Arrays.toString(text.toCharArray())
        );

        System.out.println(
                text.substring(0, 6)
        );

        System.out.println(
                text.substring(7)
        );

        System.out.println(
                text.replace("String", "Text")
        );

        System.out.println(
                text.contains("SE")
        );

        System.out.println(
                text.startsWith("Java")
        );

        String data = "Java,Python,C++";

        String[] languages = data.split(",");

        System.out.println(
                Arrays.toString(languages)
        );
    }
}

这里真正需要掌握的不是复制代码,而是能够看到需求后主动想到:

长度      → length
指定字符  → charAt
字符遍历  → charAt / toCharArray
内容比较  → equals
截取      → substring
替换      → replace
包含      → contains
前缀      → startsWith
分割      → split

四、原理与进阶

4.1 length() 不一定等于“肉眼看到的字符数量”

对于普通英文、数字以及大量常见中文字符:

"Java".length()

和我们肉眼看到的字符个数通常一致。

但 Java 的 String 底层涉及 Unicode。

length() 返回的是字符串中的:

UTF-16 code unit 数量。

某些 Unicode 字符可能需要两个 char 表示。

例如部分 Emoji 就可能出现:

肉眼看到:1 个符号

String.length():2

因此:

length()

更准确的理解应该是:

String 内部 UTF-16 字符单元的数量。

对于当前 JavaSE 入门阶段,大多数普通字符串处理仍然可以按照索引和 char 理解。

但进入国际化文本处理时,需要进一步理解:

char
Unicode Code Point
UTF-16 Code Unit

之间的区别。


4.2 为什么很多 String 方法都返回 String

例如:

substring()
replace()

都会返回:

String

原因与 String 的不可变性直接相关。

假设:

String original = "Java";
String result = original.replace("Java", "Python");

逻辑关系是:

original
   │
   └── "Java"

执行 replace

original
   │
   └── "Java"

result
   │
   └── "Python"

不是:

"Java"
  ↓
原地修改
  ↓
"Python"

这也是后续学习:

StringBuilder

的直接原因。


4.3 String API 的本质:声明式数据处理

例如手机号脱敏。

可以自己逐个字符复制。

但使用:

substring()

更清晰。

因为 API 本身已经表达:

我要截取字符串

这就是学习 API 的价值之一:

用更高层、更接近业务意图的方式表达程序。


五、实践应用

5.1 登录校验

系统保存:

String correctUsername = "itheima";
String correctPassword = "123456";

用户输入:

String username;
String password;

核心判断:

if (correctUsername.equals(username)
        && correctPassword.equals(password)) {
    System.out.println("登录成功");
}

这里必须使用:

equals()

而不是依赖:

==

5.2 文本关键词检测

例如博客标题:

String title = "JavaSE String 完整教程";

判断是否为 Java 内容:

if (title.contains("Java")) {
    System.out.println("Java相关文章");
}

5.3 数据解析

假设后端收到:

1001,星雨笔录,JavaSE

可以:

String data = "1001,星雨笔录,JavaSE";

String[] parts = data.split(",");

String id = parts[0];
String name = parts[1];
String category = parts[2];

这种思维以后会大量出现在:

文件读取
网络数据
CSV
日志
简单协议解析

等场景中。

真正复杂的数据交换则会进一步使用:

JSON
XML
对象序列化
HTTP

等技术。


六、常见问题

6.1 String 方法调用后为什么原字符串没有变化?

错误:

String s = "Java";

s.replace("Java", "Python");

System.out.println(s);

String 不可变。

应该接收返回结果:

s = s.replace("Java", "Python");

6.2 为什么 substring 经常越界?

例如:

String text = "Java";

text.substring(0, 5);

字符串长度:

4

最大可作为结束边界的是:

4

因此:

substring(0, 4)

合法。

核心规则:

0 <= beginIndex <= endIndex <= length()

6.3 substring 为什么是“包前不包后”?

例如:

substring(2, 5)

取:

2
3
4

不取:

5

结果长度:

5 - 2 = 3

这种半开区间:

[start, end)

在 Java API 和算法中非常常见。


6.4 为什么不能使用 == 判断用户输入的字符串?

因为:

==

针对引用类型主要比较引用关系。

业务中要比较字符串内容,应使用:

equals()

6.5 split(".") 为什么结果不符合预期?

因为:

split()

参数是正则表达式。

. 在正则表达式中具有特殊意义。

分割普通点号通常使用:

split("\\.")

6.6 charAt(length()) 为什么越界?

假设:

String s = "Java";

长度:

4

但合法索引:

0
1
2
3

因此:

s.charAt(s.length())

相当于:

s.charAt(4)

越界。

正确遍历:

for (int i = 0; i < s.length(); i++) {
}

6.7 replace 和 replaceAll 是不是一样?

不是。

当前章节重点掌握:

replace()

例如:

text.replace("Java", "Python");

它按照指定的字面字符序列进行替换。

而:

replaceAll()

的第一个参数使用的是:

正则表达式

因此不能因为名字相似就认为它们完全等价。


七、练习与验收

7.1 知识问答

  1. String 常用 API 主要解决哪些字符串处理问题?
  2. length() 的返回值表示什么?
  3. charAt(index) 的合法索引范围是什么?
  4. charAt()toCharArray() 在字符串遍历中有什么区别?
  5. ==equals() 比较 String 时有什么区别?
  6. equalsIgnoreCase() 适用于什么场景?
  7. substring(beginIndex, endIndex) 为什么叫“包前不包后”?
  8. substring(beginIndex, endIndex) 得到的字符串长度如何计算?
  9. 为什么调用 replace() 后原 String 不会发生改变?
  10. contains()startsWith() 分别判断什么?
  11. split() 返回什么类型?
  12. 为什么 split(".") 可能无法实现“按照点号分割”?
  13. String 不可变与大量字符串修改之间有什么关系?
  14. 为什么 length() 在处理某些 Emoji 时可能和肉眼看到的字符数量不同?

7.2 代码阅读

阅读代码,不运行程序,写出结果并说明原因:

public class Test {
    public static void main(String[] args) {
        String text = "JavaSE";

        System.out.println(text.length());
        System.out.println(text.charAt(4));
        System.out.println(text.substring(0, 4));
        System.out.println(text.substring(4));
        System.out.println(text.contains("SE"));
        System.out.println(text.startsWith("Java"));
    }
}

继续阅读:

public class Test {
    public static void main(String[] args) {
        String text = "Java";

        text.replace("Java", "Python");

        System.out.println(text);

        String result = text.replace("Java", "Python");

        System.out.println(result);
    }
}

回答:

  1. 两次输出分别是什么?
  2. 为什么第一次调用 replace() 没有改变 text
  3. resulttext 是否表示相同内容?

7.3 手写代码

任务一:手机号脱敏

给定:

18612346520

输出:

186****6520

要求:

  • 使用 substring()
  • 不允许直接写最终结果

任务二:字符统计

输入任意字符串,统计其中:

  • 英文字母数量
  • 数字数量
  • 空格数量
  • 其他字符数量

要求:

  • 使用 length() + charAt()

或者:

  • 使用 toCharArray()

任务三:用户名校验

定义:

正确用户名:admin

接收用户输入。

要求:

  • 使用字符串内容比较
  • 正确时输出登录成功
  • 错误时输出用户名错误

任务四:字符串分割

给定:

Java,Python,C++,JavaScript

将其分割后逐个输出语言名称。


7.4 Debug

下面代码存在问题:

public class DebugDemo {
    public static void main(String[] args) {
        String username = new String("admin");

        if (username == "admin") {
            System.out.println("登录成功");
        }

        for (int i = 0; i <= username.length(); i++) {
            System.out.println(username.charAt(i));
        }
    }
}

要求:

  1. 找出全部问题。
  2. 说明每个问题属于什么类型。
  3. 修改代码。
  4. 解释为什么修改后的代码正确。

继续 Debug:

String url = "www.yulanlin.cn";

String[] parts = url.split(".");

要求:

  1. 判断代码的设计意图。
  2. 分析为什么 split(".") 存在问题。
  3. 修改代码,使其按照普通点号完成分割。

7.5 综合训练

设计一个简单的用户注册信息处理程序。

用户输入:

用户名
手机号
邮箱

程序完成:

  1. 判断用户名是否为空。
  2. 输出用户名长度。
  3. 手机号进行脱敏。
  4. 判断邮箱中是否包含 @
  5. 判断邮箱是否以指定域名结尾。
  6. 输出处理后的用户信息。

要求:

  • 先写需求分析。
  • 再确定分别需要哪些 String API。
  • 最后完成程序。

7.6 本章验收

完成本章后,应能够闭卷回答:

length
charAt
toCharArray
equals
equalsIgnoreCase
substring
replace
contains
startsWith
split

分别解决什么问题。

同时能够:

  • 手写手机号脱敏。
  • 手写字符串遍历。
  • 正确比较用户输入字符串。
  • 根据分隔符拆分字符串。
  • Debug 字符串索引越界。
  • 解释为什么 String API 经常需要接收返回值。
  • 解释 split() 与普通字符串分割思维之间的正则表达式边界。