如何在Java中将CamelCase转换为易读的名称?

171

我想写一个方法,将驼峰命名法转换为易读的名称。

这是测试用例:

public void testSplitCamelCase() {
    assertEquals("lowercase", splitCamelCase("lowercase"));
    assertEquals("Class", splitCamelCase("Class"));
    assertEquals("My Class", splitCamelCase("MyClass"));
    assertEquals("HTML", splitCamelCase("HTML"));
    assertEquals("PDF Loader", splitCamelCase("PDFLoader"));
    assertEquals("A String", splitCamelCase("AString"));
    assertEquals("Simple XML Parser", splitCamelCase("SimpleXMLParser"));
    assertEquals("GL 11 Version", splitCamelCase("GL11Version"));
}

5
首先,您需要指定转换规则。例如,PDFLoader 如何变成 PDF Loader - Jørn Schou-Rode
3
我把这种格式称为“PascalCase”。在“camelCase”中,第一个字母应该是小写的,至少对于开发人员来说是这样。 http://msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx - Muhd
12个回答

360

这个可以通过你的测试用例实现:

static String splitCamelCase(String s) {
   return s.replaceAll(
      String.format("%s|%s|%s",
         "(?<=[A-Z])(?=[A-Z][a-z])",
         "(?<=[^A-Z])(?=[A-Z])",
         "(?<=[A-Za-z])(?=[^A-Za-z])"
      ),
      " "
   );
}

这里是一个测试框架:

    String[] tests = {
        "lowercase",        // [lowercase]
        "Class",            // [Class]
        "MyClass",          // [My Class]
        "HTML",             // [HTML]
        "PDFLoader",        // [PDF Loader]
        "AString",          // [A String]
        "SimpleXMLParser",  // [Simple XML Parser]
        "GL11Version",      // [GL 11 Version]
        "99Bottles",        // [99 Bottles]
        "May5",             // [May 5]
        "BFG9000",          // [BFG 9000]
    };
    for (String test : tests) {
        System.out.println("[" + splitCamelCase(test) + "]");
    }

它使用零长度匹配的正则表达式来查找需要插入空格的位置,其中包含后顾和前瞻。基本上有3个模式,我使用 String.format 将它们组合在一起以使其更易读。

这三种模式分别是:

大写字母在我的后面,后面跟着小写字母

  XMLParser   AString    PDFLoader
    /\        /\           /\

身后非UC,身前有UC

 MyClass   99Bottles
  /\        /\

我后面跟着一个字母,前面不是字母

 GL11    May5    BFG9000
  /\       /\      /\

参考资料

相关问题

使用零宽度匹配断言进行分割:


1
这个概念在C#中同样适用(使用相同的正则表达式,但当然是稍微不同的正则表达式框架)。做得非常好。谢谢! - gmm
在Python上似乎对我不起作用,可能是因为正则表达式引擎不一样。我恐怕得尝试做一些不太优雅的事情了。 :) - MarioVilas
2
有人能解释一下%s|%s|%s在测试用例中和一般情况下的含义吗? - Ari53nN3o
1
@Ari53nN3o:*"%s"*是String.format(String format, args...)的占位符。您还可以通过索引调用:String.format("%$1s|%$2s|%$3s", ... - Mr. Polywhirl
这在 C# 中如何实现?没有 relaceAll,如果字符串中有 ".",我还想添加分割。 - sarojanand
显示剩余4条评论

152

你可以使用org.apache.commons.lang.StringUtils来实现。

StringUtils.join(
     StringUtils.splitByCharacterTypeCamelCase("ExampleTest"),
     ' '
);

12
这种解决方案比得票最高的那个好得多,因为: a)它没有重新发明轮子:commons-lang是事实上的标准,而且运行良好,非常注重性能。 b)当需要进行许多次转换时,这种方法比基于正则表达式的方法快得多:这是我执行上述测试100,000次的基准:基于正则表达式的方法花费了4820毫秒////////// 基于commons-lang的方法花费了232毫秒 那是使用正则表达式的方法的大约20倍快!!! - Clint Eastwood
3
我完全同意Clint的观点,这应该成为被接受的答案。性能是重要的,但使用经过实战考验的库绝对是良好的编程实践。 - Julien
1
或者使用Java 8的String.join()方法:String.join(" ", StringUtils.splitByCharacterTypeCamelCase("ExampleTest")); - dk7

28

简洁而短小的解决方案:

StringUtils.capitalize(StringUtils.join(StringUtils.splitByCharacterTypeCamelCase("yourCamelCaseText"), StringUtils.SPACE)); // Your Camel Case Text

正如问题中第一个 assert 所示,不希望使用大写字母。 - slartidan
1
感谢您发现了这个错误,我会更新答案。 - Sahil Chhabra

12

如果你不喜欢“复杂”的正则表达式,而且完全不关心效率,那么我使用这个示例分为三个阶段来实现相同的效果。

String name = 
    camelName.replaceAll("([A-Z][a-z]+)", " $1") // Words beginning with UC
             .replaceAll("([A-Z][A-Z]+)", " $1") // "Words" of only UC
             .replaceAll("([^A-Za-z ]+)", " $1") // "Words" of non-letters
             .trim();

它通过了上面所有的测试用例,包括那些带有数字的。

就像我说的那样,这并不像其他一些示例中使用单个正则表达式那么好 - 但是某人可能会发现它有用。


1
谢谢,这太棒了。我制作了一个JavaScript版本 - Mr. Polywhirl
这也是唯一的方法,如果你正在使用一个不支持向后/向前查找的正则表达式库/工具(如golang的regexp包)。干得好。 - Michael Whatcott
这也适用于最小的JDK,例如GWT。谢谢! - Craigo

6

您可以使用org.modeshape.common.text.Inflector

具体来说:

String humanize(String lowerCaseAndUnderscoredWords,
    String... removableTokens) 

Capitalizes the first word and turns underscores into spaces and strips trailing "_id" and any supplied removable tokens.

Maven构件为:org.modeshape:modeshape-common:2.3.0.Final 在JBoss仓库中:https://repository.jboss.org/nexus/content/repositories/releases 这是JAR文件:https://repository.jboss.org/nexus/content/repositories/releases/org/modeshape/modeshape-common/2.3.0.Final/modeshape-common-2.3.0.Final.jar

1
以下正则表达式可用于识别单词内的大写字母:
"((?<=[a-z0-9])[A-Z]|(?<=[a-zA-Z])[0-9]]|(?<=[A-Z])[A-Z](?=[a-z]))"

它匹配每个大写字母,即在非大写字母或数字后面或在小写字母后面跟随的情况下,以及字母后面的每个数字。

如何在它们之前插入空格超出了我的Java技能 =)

编辑以包括数字情况和PDF加载器情况。


@Yaneeve: 我刚看到这些数字...这可能会让事情变得更加复杂。也许另一个正则表达式来捕获它们是简单的方法。 - Jens
@Jens:它会匹配PDFLoader中的“L”吗? - Jørn Schou-Rode
@Jørn:好观点!需要考虑一下。=)...好的,编辑了一些内容来解决这个问题。 - Jens
3
我非常钦佩你的正则表达式技能,但我不想去维护那个。 - Chris Knight
1
@Chris:没错,正则表达式更像是一种只能写不能读的语言。=)虽然这个特定的表达式并不难读,如果你把 | 看作“或”。嗯...也许有点难读吧...我见过更糟糕的 =/ - Jens
显示剩余3条评论

1

这适用于.NET...你可以按照自己的喜好进行优化。我添加了注释,以便你理解每个部分正在做什么。(正则表达式可能很难理解)

public static string SplitCamelCase(string str)
{
    str = Regex.Replace(str, @"([A-Z])([A-Z][a-z])", "$1 $2");  // Capital followed by capital AND a lowercase.
    str = Regex.Replace(str, @"([a-z])([A-Z])", "$1 $2"); // Lowercase followed by a capital.
    str = Regex.Replace(str, @"(\D)(\d)", "$1 $2"); //Letter followed by a number.
    str = Regex.Replace(str, @"(\d)(\D)", "$1 $2"); // Number followed by letter.
    return str;
}

1
我认为你需要遍历字符串并检测从小写到大写、大写到小写、字母到数字、数字到字母的变化。在每次检测到变化时,插入一个空格,但有一个例外:在从大写字母转换为小写字母时,将空格插入到前一个字符。

0

仅供参考,这里是一个几乎(*)兼容的Scala版本:

  object Str { def unapplySeq(s: String): Option[Seq[Char]] = Some(s) }

  def splitCamelCase(str: String) =
    String.valueOf(
      (str + "A" * 2) sliding (3) flatMap {
        case Str(a, b, c) =>
          (a.isUpper, b.isUpper, c.isUpper) match {
            case (true, false, _) => " " + a
            case (false, true, true) => a + " "
            case _ => String.valueOf(a)
          }
      } toArray
    ).trim

一旦编译完成,如果相应的scala-library.jar在类路径中,则可以直接从Java中使用。

(*) 对于输入的"GL11Version",它会失败并返回"G L11 Version"


0
我将来自polygenelubricants的正则表达式转换为对象的扩展方法:
    /// <summary>
    /// Turns a given object into a sentence by:
    /// Converting the given object into a <see cref="string"/>.
    /// Adding spaces before each capital letter except for the first letter of the string representation of the given object.
    /// Makes the entire string lower case except for the first word and any acronyms.
    /// </summary>
    /// <param name="original">The object to turn into a proper sentence.</param>
    /// <returns>A string representation of the original object that reads like a real sentence.</returns>
    public static string ToProperSentence(this object original)
    {
        Regex addSpacesAtCapitalLettersRegEx = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
        string[] words = addSpacesAtCapitalLettersRegEx.Split(original.ToString());
        if (words.Length > 1)
        {
            List<string> wordsList = new List<string> { words[0] };
            wordsList.AddRange(words.Skip(1).Select(word => word.Equals(word.ToUpper()) ? word : word.ToLower()));
            words = wordsList.ToArray();
        }
        return string.Join(" ", words);
    }

这将把所有内容转换为可读的句子。它对传递的对象执行 ToString 操作。然后使用 polygenelubricants 提供的正则表达式拆分字符串。然后 ToLowers 每个单词,除了第一个单词和任何缩写词。希望这对某些人有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接