如何在Java中将CamelCase转换为人类可读的名称?


157

我想编写一种将CamelCase转换为人类可读名称的方法。

这是测试用例:

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ørnSchou-Rode)2010年

2
我称这种格式为“ PascalCase”。在“ camelCase”中,首字母应小写。至少就开发人员而言。msdn.microsoft.com/zh-CN/library/x2dbyw72(v=vs.71).aspx
Muhd

Answers:


337

这适用于您的测试用例:

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将它们放在一起以使其更具可读性。

这三种模式是:

UC在我后面,UC在我前面紧跟着LC

  XMLParser   AString    PDFLoader
    /\        /\           /\

非UC在我身后,UC在我身前

 MyClass   99Bottles
  /\        /\

我身后的信件,我前面的非字母

 GL11    May5    BFG9000
  /\       /\      /\

参考文献

相关问题

使用零长度匹配查找方法进行拆分:


1
该概念也适用于C#(当然,它们具有相同的正则表达式,但正则表达式框架略有不同)。优秀作品。谢谢!
gmm 2013年

在Python上似乎不适用于我,这可能是因为正则表达式引擎不相同。恐怕我将不得不尝试做一些不太优雅的事情。:)
MarioVilas

2
有人可以解释一下%s |%s |%s对测试用例的含义吗?
Ari53​​nN3o 2014年

1
@ Ari53​​nN3o:%sString.format(String format, args...)参数的占位符。您也可以按指数致电:String.format("%$1s|%$2s|%$3s", ...
Polywhirl先生,2015年

这在c#中将如何工作?有没有relaceAll我也想加分,如果字符串中有“ .在那”。
sarojanand 2015年

119

您可以使用 org.apache.commons.lang.StringUtils

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

9
此解决方案比最受好评的解决方案要好得多,因为:a)不会重新发明轮子:commons-lang是事实上的标准,并且工作良好,非常注重性能。b)当转换完成很多次时,此方法比基于正则表达式的方法快得多:这是我执行上述测试100,000次的基准:```基于正则表达式的方法花费了4820毫秒////// //////基于commons-lang的方法耗时232毫秒,比使用regex的方法快20倍!!!
克林特·伊斯特伍德

2
我绝对同意克林特在这一点上的看法,这应该是公认的答案。性能是一回事,但是使用经过考验的库绝对是一种不错的编程习惯。
朱利安

1
或使用Java 8的String.join()方法:String.join(“”,StringUtils.splitByCharacterTypeCamelCase(“ ExampleTest”)));
dk7

你怎么不同意克林特·伊斯特伍德?:)
daneejela

19

简洁明了的解决方案:

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

如第一个assert问题所示,不要求使用大写字母。
slartidan

感谢您捕获错误,将更新答案。
Sahil Chhabra

10

如果您不喜欢“复杂的”正则表达式,并且完全不关心效率,那么我已使用此示例在三个阶段中实现了相同的效果。

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版本
Polywhirl先生,2015年

如果您正在使用不支持后向/前向(例如golang的regexp包)的正则表达式库/工具,这也是唯一的选择。辛苦了
mdwhatcott

6

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

特别:

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

将第一个单词大写,然后将下划线变成空格,并在结尾加上“ _id”和任何提供的可移动令牌。

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 Loader大小写。


@Yaneeve:我刚刚看到了数字……这可能会使事情变得更复杂。也许另一个正则表达式来捕获这些将是简单的方法。
詹斯

@Jens:会匹配Lin PDFLoader吗?
约恩·舒德罗德(JørnSchou-Rode)2010年

(?<= [a-z0-9])[A-Z0-9]怎么样?
Yaneeve

3
现在,我非常欣赏您的Regex技能,但是我讨厌必须保持这种技能。
克里斯·奈特

1
@克里斯:是的,那是真的。正则表达式更像是只写语言。=)尽管此特殊表达式不是很难读,但是如果您将其读|为“或”。好吧...也许是...我看得更糟= /
Jens

1

我认为您将必须遍历字符串并检测从小写到大写,大写到小写,字母到数字,数字到字母的变化。但是,在每次更改时,您都会检测到插入一个空格,但有一个例外:从大写到小写的更改,您都在空格前插入了一个字符。


1

可以在.NET中使用...根据您的喜好进行优化。我添加了评论,以便您了解每个部分在做什么。(RegEx可能很难理解)

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;
}

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

我从多基因润滑剂中提取了正则表达式,并将其转换为对象的扩展方法:

    /// <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给定的Regex分割字符串。然后,它会降低除第一个单词和首字母缩写词以外的每个单词。认为这对外面的人可能有用。


-2

我不是正则表达式忍者,所以我要遍历字符串,保持检查当前位置和上一个位置的索引。如果当前职位是一个大写字母,我会在前一个职位之后插入一个空格,并增加每个索引。


2
s!那里的乐趣在哪里?
vbullinger 2012年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.