1. 概述

字符串通常包含单词和其他分隔符的混合。有时,这些字符串可能会通过更改大小写而不使用空格来分隔单词。例如, 驼峰式大小写将第一个之后的每个单词大写 ,标题大小写(或帕斯卡大小写)将每个单词大写。

我们可能希望将这些字符串解析回单词以便处理它们。

在这个简短的教程中,我们将了解如何使用正则表达式查找混合大小写字符串中的单词,以及如何将它们转换为句子或标题。

2. 解析大写字符串的用例

处理驼峰式字符串的常见用例可能是文档中的字段名称。假设文档有一个字段 “firstName”—— 我们可能希望在屏幕上将其显示为“First name”或“First Name”

同样,如果我们要通过反射扫描应用程序中的类型或函数,以便使用它们的名称生成报告,我们通常会找到我们可能希望转换的驼峰式或标题式标识符。

解析这些表达式时我们需要解决的一个额外问题是 单字母单词会导致连续的大写字母

为了清楚起见:

  • 这是骆驼案例的一个例子
  • 这是标题大小写
  • 这个有一个单字母单词

现在我们知道了需要解析的标识符类型,让我们使用正则表达式来查找单词。

3. 使用正则表达式查找单词

3.1.定义正则表达式来查找单词

让我们定义一个正则表达式来查找仅由小写字母、单个大写字母后跟小写字母或单独由单个大写字母组成的单词:

Pattern WORD_FINDER = Pattern.compile("(([A-Z]?[a-z]+)|([A-Z]))");

该表达式为正则表达式引擎提供了两个选项。第一个使用 “[AZ]?” 表示“可选的第一个大写字母”,然后 “[az]+” 表示“一个或多个小写字母”。之后就是 “|” 提供 逻辑的字符,后跟表达式 “[AZ]” ,表示“单个大写字母”。

现在我们有了正则表达式,让我们解析字符串。

3.2.查找字符串中的单词

我们将定义一个方法来使用此正则表达式:

public List<String> findWordsInMixedCase(String text) {
    Matcher matcher = WORD_FINDER.matcher(text);
    List<String> words = new ArrayList<>();
    while (matcher.find()) {
        words.add(matcher.group(0));
    }
    return words;
}

这使用正则表达式的 Pattern 创建的 Matcher 来帮助我们找到单词当匹配器仍然有匹配项时,我们迭代它 ,将它们添加到我们的列表中。

这应该提取任何符合我们的单词定义的内容。我们来测试一下。

3.3.测试单词查找器

我们的单词查找器应该能够找到由任何非单词字符以及大小写变化分隔的单词。让我们从一个简单的例子开始:

assertThat(findWordsInMixedCase("some words"))
  .containsExactly("some", "words");

该测试通过并表明我们的算法正在运行。接下来,我们将尝试骆驼案例:

assertThat(findWordsInMixedCase("thisIsCamelCaseText"))
  .containsExactly("this", "Is", "Camel", "Case", "Text");

在这里我们看到单词是从驼峰式字符串中提取出来的,并且大小写不变。例如, “Is” 在原文中以大写字母开头,提取后变为大写。

我们还可以尝试使用标题大小写:

assertThat(findWordsInMixedCase("ThisIsTitleCaseText"))
  .containsExactly("This", "Is", "Title", "Case", "Text");

另外,我们可以检查单字母单词是否按照我们的预期提取:

assertThat(findWordsInMixedCase("thisHasASingleLetterWord"))
  .containsExactly("this", "Has", "A", "Single", "Letter", "Word");

到目前为止,我们已经构建了一个单词提取器,但这些单词的大写方式可能不适合输出。

4. 将单词列表转换为人类可读的格式

提取单词列表后,我们可能想要使用 toUpperCasetoLowerCase 等方法来规范化它们。然后我们可以使用 String.join 将它们连接回带有分隔符的单个字符串。让我们看一下用这些实现实际用例的几种方法。

4.1.转换为句子

句子以大写字母开头,以句号结尾 - “.” 。我们需要能够使单词以大写字母开头:

private String capitalizeFirst(String word) {
    return word.substring(0, 1).toUpperCase()
      + word.substring(1).toLowerCase();
}

然后我们可以循环遍历单词,将第一个大写,并将其他小写:

public String sentenceCase(List<String> words) {
    List<String> capitalized = new ArrayList<>();
    for (int i = 0; i < words.size(); i++) {
        String currentWord = words.get(i);
        if (i == 0) {
            capitalized.add(capitalizeFirst(currentWord));
        } else {
            capitalized.add(currentWord.toLowerCase());
        }
    }
    return String.join(" ", capitalized) + ".";
}

这里的逻辑是第一个单词的第一个字符大写,其余字符小写。我们用空格作为分隔符将它们连接起来,并在末尾添加一个句点。

让我们测试一下:

assertThat(sentenceCase(Arrays.asList("these", "Words", "Form", "A", "Sentence")))
  .isEqualTo("These words form a sentence.");

4.2.转换为标题大小写

标题大小写的规则比句子稍微复杂一些。 每个单词都必须有一个大写字母,除非它是通常不大写的特殊停用词 。但是,整个标题必须以大写字母开头。

我们可以通过定义停用词来实现这一点:

Set<String> STOP_WORDS = Stream.of("a", "an", "the", "and", 
  "but", "for", "at", "by", "to", "or")
  .collect(Collectors.toSet());

之后,我们可以修改循环中的 if 语句,将所有不是停用词的单词以及第一个单词大写:

if (i == 0 || 
  !STOP_WORDS.contains(currentWord.toLowerCase())) {
    capitalized.add(capitalizeFirst(currentWord));
 }

组合单词的算法是相同的,尽管我们最后不添加句点。

让我们测试一下:

assertThat(capitalizeMyTitle(Arrays.asList("title", "words", "capitalize")))
  .isEqualTo("Title Words Capitalize");

assertThat(capitalizeMyTitle(Arrays.asList("a", "stop", "word", "first")))
  .isEqualTo("A Stop Word First");

5. 结论

在这篇简短的文章中,我们了解了如何使用正则表达式在 字符串 中查找单词。我们了解了如何定义此正则表达式以使用大写作为单词边界来查找不同的单词。

我们还研究了一些简单的算法,用于获取单词列表并将它们转换为句子或标题的正确大写形式。

与往常一样,示例代码可以在 GitHub 上找到。