Java substring() 方法(深入浅出)

Java substring() 方法详解:从入门到精通

在 Java 编程中,字符串操作是日常开发中最为频繁的任务之一。无论是处理用户输入、解析日志文件,还是从 URL 中提取参数,我们几乎都会用到字符串的截取功能。而 Java 提供的 substring() 方法,正是完成这项任务的核心工具。它看似简单,实则蕴含着许多容易被忽视的细节。本文将带你系统地掌握 Java substring() 方法,从基础用法到边界问题,从性能考量到实际应用场景,全面解析这个看似普通却极为重要的方法。

什么是 Java substring() 方法?

substring()String 类中的一个实例方法,用于从一个字符串中提取指定范围的子字符串。你可以把它想象成一把“精准的裁纸刀”——你给定起点和终点,它就能从原字符串中剪下你想要的那一段。

这个方法有两个重载版本:

  • substring(int beginIndex):从指定索引开始,截取到字符串末尾。
  • substring(int beginIndex, int endIndex):从 beginIndex 开始,到 endIndex 结束(不包含 endIndex 位置的字符)。

注意:Java 的字符串索引从 0 开始,这一点和大多数编程语言保持一致。

基础用法与代码示例

我们先来看一个最简单的例子,帮助你建立直观理解。

public class SubstringExample {
    public static void main(String[] args) {
        String text = "Hello, World! Welcome to Java programming.";

        // 示例 1:从索引 7 开始,截取到末尾
        String result1 = text.substring(7);
        System.out.println("从索引 7 开始:" + result1);
        // 输出:World! Welcome to Java programming.

        // 示例 2:从索引 0 开始,到索引 5 结束(不包含索引 5)
        String result2 = text.substring(0, 5);
        System.out.println("从 0 到 5(不包含):" + result2);
        // 输出:Hello

        // 示例 3:从索引 13 开始,到索引 25 结束
        String result3 = text.substring(13, 25);
        System.out.println("从 13 到 25(不包含):" + result3);
        // 输出:Welcome to J
    }
}

重点说明:

  • substring(7) 表示从第 8 个字符(索引 7)开始,一直截取到字符串末尾。
  • substring(0, 5) 表示从第 1 个字符(索引 0)开始,到第 6 个字符(索引 5)前结束,不包含索引 5 的字符
  • 索引范围是左闭右开的,即 [beginIndex, endIndex),这是 Java 中常见的区间表示方式。

常见陷阱与边界处理

虽然 substring() 方法使用简单,但在实际开发中,边界问题常常引发 StringIndexOutOfBoundsException 异常。我们来深入分析几种常见情况。

越界问题

String str = "Java";

// 错误用法:索引超出范围
try {
    String result = str.substring(10); // 索引 10 超过字符串长度 4
    System.out.println(result);
} catch (StringIndexOutOfBoundsException e) {
    System.out.println("错误:索引超出范围!" + e.getMessage());
    // 输出:错误:索引超出范围!String index out of range: 10
}

⚠️ 提示:调用 substring() 前,建议先检查索引是否在合法范围内(0 ≤ index < length)。

起始索引大于结束索引

String text = "Hello";

try {
    String sub = text.substring(3, 1); // 起始索引 > 结束索引
} catch (IllegalArgumentException e) {
    System.out.println("错误:起始索引不能大于结束索引!");
    // 输出:错误:起始索引不能大于结束索引!
}

这种情况会抛出 IllegalArgumentException,说明 beginIndex 必须小于或等于 endIndex

空字符串与边界情况

String empty = "";

try {
    empty.substring(0, 1); // 空字符串长度为 0,索引 1 无效
} catch (StringIndexOutOfBoundsException e) {
    System.out.println("空字符串无法截取:" + e.getMessage());
}

小贴士:在使用 substring() 之前,建议先判断字符串是否为空,避免运行时异常。

实际应用场景

1. 提取文件扩展名

在处理文件路径时,我们经常需要提取文件后缀名,比如 .txt.jpg

public class FileExtensionExtractor {
    public static String getExtension(String filename) {
        // 如果文件名中没有点号,返回空字符串
        if (!filename.contains(".")) {
            return "";
        }
        
        // 找到最后一个点的位置
        int lastDotIndex = filename.lastIndexOf('.');
        
        // 从最后一个点之后开始截取,直到末尾
        return filename.substring(lastDotIndex + 1);
    }

    public static void main(String[] args) {
        System.out.println(getExtension("document.txt"));     // 输出:txt
        System.out.println(getExtension("image.png"));       // 输出:png
        System.out.println(getExtension("no_extension"));    // 输出:空字符串
    }
}

2. 截取 URL 参数

从 URL 中提取查询参数(query string)是 Web 开发中的常见需求。

public class URLParameterExtractor {
    public static String extractQueryParam(String url, String paramKey) {
        int queryStart = url.indexOf('?');
        if (queryStart == -1) {
            return null; // 没有查询参数
        }

        // 截取查询部分,例如:?name=Tom&age=25
        String query = url.substring(queryStart + 1);

        // 按 & 分割参数
        String[] params = query.split("&");

        // 遍历每个参数,查找匹配 key
        for (String p : params) {
            if (p.startsWith(paramKey + "=")) {
                return p.substring(paramKey.length() + 1); // 去掉 key= 部分
            }
        }
        return null;
    }

    public static void main(String[] args) {
        String url = "https://example.com/search?name=Tom&age=25&city=Beijing";
        System.out.println(extractQueryParam(url, "name"));  // 输出:Tom
        System.out.println(extractQueryParam(url, "age"));   // 输出:25
    }
}

性能与内存考量

虽然 substring() 方法使用方便,但它的实现方式可能带来性能隐患。在 Java 8 及之前的版本中,substring() 方法会复用原字符串的字符数组(value 字段),这意味着即使你只想要一小段内容,整个原始字符串仍会被保留,导致内存占用无法释放。

举例说明

String longText = "这是一个非常长的字符串,包含很多无关内容,但我们需要提取其中的一部分。".repeat(1000);

String shortPart = longText.substring(10, 50); // 只取前 40 个字符

// 问题:longText 的整个字符数组仍被引用,无法被 GC 回收

📌 优化建议:如果你只使用子字符串,并且原字符串非常大,可以手动创建新字符串以避免内存泄漏。

// 修复方式:显式复制字符数组
String shortPart = new String(longText.substring(10, 50));

这个小技巧在处理大文件、日志解析等场景中尤为重要。

方法对比与替代方案

方法 特点 适用场景
substring(int beginIndex) 从指定索引截取到末尾 提取后缀、末尾片段
substring(int beginIndex, int endIndex) 截取指定范围,左闭右开 提取中间内容、参数解析
split() + get() 按分隔符拆分后取某段 处理 CSV、日志行等
StringUtils.substring()(Apache Commons) 更安全,支持 null 安全处理 企业级项目,防空指针

建议:在大型项目中,可以考虑使用 Apache Commons Lang 的 StringUtils 工具类,它对 substring() 做了更完善的封装,避免空指针和越界异常。

总结

Java substring() 方法 是字符串处理中不可或缺的工具,掌握其用法不仅能提升编码效率,还能避免许多常见的运行时错误。通过本文的学习,你应该已经理解了:

  • 如何正确使用两个重载版本;
  • 如何处理边界情况,防止异常;
  • 如何在实际项目中应用,如提取扩展名、解析 URL;
  • 如何关注性能,避免内存泄漏;
  • 何时选择替代方案。

记住:看似简单的 API,往往藏着深层的设计哲学。每一次调用 substring(),都是对字符串的“精准裁剪”,也是对代码质量的考验。

下一次当你在处理字符串时,不妨停下来想一想:我是否正确地使用了 Java substring() 方法?是否考虑了边界和性能?这些细节,正是区分“会写代码”和“写好代码”的分水岭。