1. 概述

驼峰命名法(camel case)和下划线命名法(snake case)是编程中两种常见的命名规范。驼峰命名法通过大写字母标记单词边界(如 camelCaseExample),而下划线命名法则使用下划线分隔单词(如 snake_case_example)。

本教程将探讨在Java中实现这种转换的几种方法。

2. 转换原理分析

将驼峰命名转换为下划线命名需要完成三个核心步骤:

  1. 识别单词边界(即大写字母位置)
  2. 在边界处插入下划线_
  3. 将所有字母转为小写

例如:

  • 输入:"convertCamelCase"
  • 输出:"convert_camel_case"

理解这个转换逻辑后,我们来看看具体实现方案。

3. 手动实现方案

最直接的方式是遍历字符串字符,遇到大写字母时插入下划线:

public static String convertCamelCaseToSnake(String input) {
    StringBuilder result = new StringBuilder();
    for (char c : input.toCharArray()) {
        if (Character.isUpperCase(c)) {
            result.append("_").append(Character.toLowerCase(c));
        } else {
            result.append(c);
        }
    }
    return result.toString();
}

代码逻辑解析:

  1. 遍历字符串中的每个字符
  2. 遇到大写字母时
    • 先添加下划线
    • 再将该字母转为小写
  3. 其他字符直接追加

测试用例验证:

@Test
public void whenConvertNormalCamelCase_thenGetCorrectSnakeCase() {
    String input = "convertCamelCase";
    String expected = "convert_camel_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnake(input));
}

@Test
public void whenConvertNotNormalCamelCase_thenGetCorrectSnakeCase() {
    String input = "convertCCamelCase";
    String expected = "convert_c_camel_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnake(input));
}

@Test
public void whenConvertAlreadySnakeCase_thenGetUnchangedSnakeCase() {
    String input = "snake_case";
    String expected = "snake_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnake(input));
}

@Test
public void whenConvertAllLowerCaseString_thenGetUnchangedString() {
    String input = "snakecase";
    String expected = "snakecase";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnake(input));
}

@Test
public void whenConvertOtherEdgeCases_thenGetCorrectSnakeCases() {
    // 空字符串测试
    Assertions.assertEquals("", CamelToSnakeCaseConverter.convertCamelCaseToSnake(""));

    // 特殊字符测试
    String input = "sn@keCase#";
    String expected = "sn@ke_case#";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnake(input));
}

4. 正则表达式方案

利用正则表达式可以更简洁地实现转换:

public static String convertCamelCaseToSnakeRegex(String input) {
    return input
      .replaceAll("([A-Z])(?=[A-Z])", "$1_")
      .replaceAll("([a-z])([A-Z])", "$1_$2")
      .toLowerCase();
}

正则表达式解析:

  1. replaceAll("([A-Z])(?=[A-Z])", "$1_")

    • 匹配连续大写字母中的边界(如 CCC_C
    • 使用正向预查 (?=[A-Z]) 确保后面还有大写字母
  2. replaceAll("([a-z])([A-Z])", "$1_$2")

    • 匹配小写字母后跟大写字母的情况(如 aAa_A
  3. 最后统一转为小写

同样通过测试用例验证:

@Test
public void whenConvertNormalCamelCase_thenGetCorrectSnakeCase() {
    String input = "convertCamelCase";
    String expected = "convert_camel_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(input));
}

@Test
public void whenConvertNotNormalCamelCase_thenGetCorrectSnakeCase() {
    String input = "convertCCamelCase";
    String expected = "convert_c_camel_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(input));
}

@Test
public void whenConvertAlreadySnakeCase_thenGetUnchangedSnakeCase() {
    String input = "snake_case";
    String expected = "snake_case";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(input));
}

@Test
public void whenConvertAllLowerCaseString_thenGetUnchangedString() {
    String input = "snakecase";
    String expected = "snakecase";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(input));
}

@Test
public void whenConvertOtherEdgeCases_thenGetCorrectSnakeCases() {
    // 空字符串测试
    Assertions.assertEquals("", CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(""));

    // 特殊字符测试
    String input = "sn@keCase#";
    String expected = "sn@ke_case#";
    Assertions.assertEquals(expected, CamelToSnakeCaseConverter.convertCamelCaseToSnakeRegex(input));
}

5. 边界情况处理

实际开发中需要特别注意这些边界情况:

场景 预期行为 处理要点
✅ 空字符串 返回空字符串 直接返回
✅ 全小写字符串 保持不变 无需处理
✅ 已是下划线命名 保持不变 避免重复转换
⚠️ 特殊字符 保留原样 不影响字母转换

踩坑提示:连续大写字母(如 XMLHttpRequest)需要特殊处理,否则会变成 x_m_l_http_request 而非期望的 xml_http_request。本方案已通过正则表达式优化解决此问题。

6. 总结

我们探讨了两种Java中驼峰命名转下划线命名的实现方案:

  1. 手动遍历方案

    • 优点:逻辑直观,易于调试
    • 缺点:代码量稍多
  2. 正则表达式方案

    • 优点:简洁高效,一行搞定
    • 缺点:需要理解正则表达式

根据实际场景选择即可——简单场景用正则更优雅,复杂逻辑需要调试时手动方案更可控。两种方案均已通过完整测试用例验证,可直接用于生产环境。


原始标题:Convert Camel Case to Snake Case in Java | Baeldung

» 下一篇: JLine 3 介绍