用于在 java 中将 CamelCase 转换为驼峰_case 的正则表达式

我理解为什么没有给出使用正则表达式将字符串(如 FooBar)转换为 Foo_Bar所需的输出,而是给出了 Foo_Bar_。我可以使用 String.substring substring(0, string.length() - 2)做一些事情,或者只是替换最后一个字符,但是我认为对于这种情况有更好的解决方案。

密码如下:

String regex = "([A-Z][a-z]+)";
String replacement = "$1_";


"CamelCaseToSomethingElse".replaceAll(regex, replacement);


/*
outputs: Camel_Case_To_Something_Else_
desired output: Camel_Case_To_Something_Else
*/

问题: 寻找一种更简洁的方法来获得所需的输出?

89533 次浏览

Add a zero-width lookahead assertion.

http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html

Read the documentation for (?=X) etc.

Personally, I would actually split the string, then recombine it. This may even be faster when done right, and it makes the code much easier to understand than regular expression magic. Don't get me wrong: I love regular expressions. But this isn't really a neat regular expression, nor is this transformation a classic regexp task. After all it seems you also want to do lowercase?

An ugly but quick hack would be to replace (.)([A-Z]+) with $1_$2 and then lowercase the whole string afterwards (unless you can do perl-style extrended regexps, where you can lowercase the replacement directly!). Still I consider splitting at lower-to-upper transition, then transforming, then joining as the proper and most readable way of doing this.

See this question and CaseFormat from guava

in your case, something like:

CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "SomeInput");
([A-Z][a-z\d]+)(?=([A-Z][a-z\d]+))

Should search for a capital letter followed by lowercase letters. The positive lookahead will look for another word starting with a capital letter followed by lowercase letters but will NOT include it in the match.

Look here: http://regexr.com?30ooo

bind the lower case and upper case as two group,it will be ok

public  class Main
{
public static void main(String args[])
{
String regex = "([a-z])([A-Z]+)";
String replacement = "$1_$2";
System.out.println("CamelCaseToSomethingElse"
.replaceAll(regex, replacement)
.toLowerCase());
}
}

You can use below code snippet:

String replaceAll = key.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase();

Why not simply match prior character as a not start of line $?

String text = "CamelCaseToSomethingElse";
System.out.println(text.replaceAll("([^_A-Z])([A-Z])", "$1_$2"));

Note that this version is safe to be performed on something that is already camel cased.

public class ReplaceFromCameltoSnake {
public static void main(String args[]){
String s1=" totalAmountWithoutDiscount";
String replaceString=s1.replaceAll("([A-Z]+)","\\_$1").toLowerCase();
System.out.println(replaceString);
}
}

I can't provide RegEx, it would be insanely complex anyway.

Try this function with automatic recognition of acronyms.

Unfortunately Guava lib doesn't auto detect upper case acronyms, so "bigCAT" would be converted to "BIG_C_A_T"

/**
* Convert to UPPER_UNDERSCORE format detecting upper case acronyms
*/
private String upperUnderscoreWithAcronyms(String name) {
StringBuffer result = new StringBuffer();
boolean begin = true;
boolean lastUppercase = false;
for( int i=0; i < name.length(); i++ ) {
char ch = name.charAt(i);
if( Character.isUpperCase(ch) ) {
// is start?
if( begin ) {
result.append(ch);
} else {
if( lastUppercase ) {
// test if end of acronym
if( i+1<name.length() ) {
char next = name.charAt(i+1);
if( Character.isUpperCase(next) ) {
// acronym continues
result.append(ch);
} else {
// end of acronym
result.append('_').append(ch);
}
} else {
// acronym continues
result.append(ch);
}
} else {
// last was lowercase, insert _
result.append('_').append(ch);
}
}
lastUppercase=true;
} else {
result.append(Character.toUpperCase(ch));
lastUppercase=false;
}
begin=false;
}
return result.toString();
}

I've had to implement this to convert some keys in camel case format to lower case with underscores. The regular expression I came up with is:

(?<!^|_|[A-Z])([A-Z])

In english it stands for capital letter which is not preceded by the start of the string, an underscore or another capital letter.

In the samples below, the character in bold are the ones that should produce a match using the aforementioned regular expression:

  • CamelCaseToSomethingElse
  • camelCaseToSomethingElse
  • camel_case_to_something_else
  • Camel_Case_To_Something_Else
  • CAMEL_CASE_TO_SOMETHING_ELSE

Notice the expression does not affect string that are already in lower case + underscore format.

The replacement pattern would be:

_l$1

Which means lower case of first capturing group, first capturing group being the capital letter. You could lower case the whole string afterwards as well to normalize the last two samples from the list above.

Not sure it's possible to have something really solide with pure regex. Especially to support acronyms.

I have made a small function, inspired by @radzimir answer, that supports acronyms and no alphabetic character:

From https://gist.github.com/ebuildy/cf46a09b1ac43eea17c7621b7617ebcd:

private static String snakeCaseFormat(String name) {
final StringBuilder result = new StringBuilder();


boolean lastUppercase = false;


for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
char lastEntry = i == 0 ? 'X' : result.charAt(result.length() - 1);
if (ch == ' ' || ch == '_' || ch == '-' || ch == '.') {
lastUppercase = false;


if (lastEntry == '_') {
continue;
} else {
ch = '_';
}
} else if (Character.isUpperCase(ch)) {
ch = Character.toLowerCase(ch);
// is start?
if (i > 0) {
if (lastUppercase) {
// test if end of acronym
if (i + 1 < name.length()) {
char next = name.charAt(i + 1);
if (!Character.isUpperCase(next) && Character.isAlphabetic(next)) {
// end of acronym
if (lastEntry != '_') {
result.append('_');
}
}
}
} else {
// last was lowercase, insert _
if (lastEntry != '_') {
result.append('_');
}
}
}
lastUppercase = true;
} else {
lastUppercase = false;
}


result.append(ch);
}
return result.toString();
}

I am writing this answer if somebody doesn't want to use Guava as below for any reason.

CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "SomeInput");

In our case we had problem with storage. There is another special case with Guava: if we have "Ph_D" as input, then we are going to get "ph__d" with two underscores.

The code below worked as long as I tested it.

public static String camelCaseToLowerCaseWithUnderscore(String string) {
if (string.matches(".*[a-z].*")) {
final Matcher matcher = Pattern.compile("(_?[A-Z][a-z]?)").matcher(string);


StringBuffer stringBuffer = new StringBuffer();
matcher.find(); // This is just to escape the first group (beginning of string)
while (matcher.find()) {
final String group = matcher.group();
if (!group.startsWith("_")) {
matcher.appendReplacement(stringBuffer, "_" + group);
}
}
matcher.appendTail(stringBuffer);
return stringBuffer.toString().toLowerCase();
}
else {
return string;
}
}

Here my solution with 3 regular expression:

str.replaceAll("([^A-Z])([A-Z0-9])", "$1_$2") // standard replace
.replaceAll("([A-Z]+)([A-Z0-9][^A-Z]+)", "$1_$2") // last letter after full uppercase.
.replaceAll("([0-9]+)([a-zA-Z]+)", "$1_$2").toLowerCase(); // letters after numbers

The result:

thisIsATest: this_is_a_test
EndWithNumber3: end_with_number_3
3ThisStartWithNumber: 3_this_start_with_number
Number3InMiddle: number_3_in_middle
Number3inMiddleAgain: number_3_in_middle_again
MyUUIDNot: my_uuid_not
HOLAMundo: hola_mundo
holaMUNDO: hola_mundo
with_underscore: with_underscore
withAUniqueLetter: with_a_unique_letter


Edited:

To support numbers and another symbols, you can use this:

str.replaceAll("([^A-Z])([A-Z])", "$1_$2") // standard replace
.replaceAll("([A-Z]+)([^a-z][^A-Z]+)", "$1_$2") // last letter after full uppercase.
.toLowerCase()
.replaceAll("([^a-z]+)([a-z]+)", "$1_$2") // letters after non-letters.
.replaceAll("([a-z]+)([^a-z]+)", "$1_$2"); // letters before non-letters.

The result:

thisIsATest: "this_is_a_test"
EndWithNumber3: "end_with_number_3"
3ThisStartWithNumber: "3_this_start_with_number"
Number3InMiddle: "number_3_in_middle"
Number3inMiddleAgain: "number_3_in_middle_again"
MyUUIDNot: "my_uuid_not"
HOLAMundo: "hola_mundo"
holaMUNDO: "hola_mundo"
with_underscore: "with_underscore"
withAUniqueLetter: "with_a_unique_letter"
with%SYMBOLAndNumber90: "with_%_symbol_and_number_90"
http%: "http_%"
123456789: "123456789"
: "     "
_: "_"
__abc__: "__abc__"

You can easily convert String to camel case using Stream API from Java 8 and method StringUtils.capitalize(..) from commons-lang

 public String toCamelCase(String str) {
return Arrays.stream(str.split("_"))
.map(StringUtils::capitalize)
.collect(Collectors.joining());
}

Yet another solution with Apache Commons.

import org.apache.commons.lang3.StringUtils;


public static String toLowerUnderscore(String str) {
if (str == null) {
return null;
}
String[] tokens = StringUtils.splitByCharacterTypeCamelCase(str);
String joined = StringUtils.join(tokens, '\t');
String replaced =  joined
.replace("_\t", "_") // save beginning underscore
.replace("\t_", "_") // save ending underscore
.replace("\t", "_"); // replace other underscores
return replaced.toLowerCase();
}

Test cases (thanks @Ali):

thisIsATest:          this_is_a_test
EndWithNumber3:       end_with_number_3
3ThisStartWithNumber: 3_this_start_with_number
Number3InMiddle:      number_3_in_middle
Number3inMiddleAgain: number_3_in_middle_again
MyUUIDNot:            my_uuid_not
HOLAMundo:            hola_mundo
holaMUNDO:            hola_mundo
with_underscore:      with_underscore
withAUniqueLetter:    with_a_unique_letter
123456789:            123456789
"   ":                "   "
_:                    _
__abc__:              __abc__
null:                 null