Most efficient way to make the first character of a String lower case?
Solution 1:
I tested the promising approaches using JMH. Full benchmark code.
Assumption during the tests (to avoid checking the corner cases every time): the input String length is always greater than 1.
Results
Benchmark Mode Cnt Score Error Units
MyBenchmark.test1 thrpt 20 10463220.493 ± 288805.068 ops/s
MyBenchmark.test2 thrpt 20 14730158.709 ± 530444.444 ops/s
MyBenchmark.test3 thrpt 20 16079551.751 ± 56884.357 ops/s
MyBenchmark.test4 thrpt 20 9762578.446 ± 584316.582 ops/s
MyBenchmark.test5 thrpt 20 6093216.066 ± 180062.872 ops/s
MyBenchmark.test6 thrpt 20 2104102.578 ± 18705.805 ops/s
The score are operations per second, the more the better.
Tests
-
test1
was first Andy's and Hllink's approach:string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
-
test2
was second Andy's approach. It is alsoIntrospector.decapitalize()
suggested by Daniel, but without twoif
statements. Firstif
was removed because of the testing assumption. The second one was removed, because it was violating correctness (i.e. input"HI"
would return"HI"
). This was almost the fastest.char c[] = string.toCharArray(); c[0] = Character.toLowerCase(c[0]); string = new String(c);
-
test3
was a modification oftest2
, but instead ofCharacter.toLowerCase()
, I was adding 32, which works correctly if and only if the string is in ASCII. This was the fastest.c[0] |= ' '
from Mike's comment gave the same performance.char c[] = string.toCharArray(); c[0] += 32; string = new String(c);
-
test4
usedStringBuilder
.StringBuilder sb = new StringBuilder(string); sb.setCharAt(0, Character.toLowerCase(sb.charAt(0))); string = sb.toString();
-
test5
used twosubstring()
calls.string = string.substring(0, 1).toLowerCase() + string.substring(1);
-
test6
uses reflection to changechar value[]
directly in String. This was the slowest.try { Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(string); value[0] = Character.toLowerCase(value[0]); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); }
Conclusions
If the String length is always greater than 0, use test2
.
If not, we have to check the corner cases:
public static String decapitalize(String string) {
if (string == null || string.length() == 0) {
return string;
}
char c[] = string.toCharArray();
c[0] = Character.toLowerCase(c[0]);
return new String(c);
}
If you are sure that your text will be always in ASCII and you are looking for extreme performance because you found this code in the bottleneck, use test3
.
Solution 2:
I came across a nice alternative if you don't want to use a third-party library:
import java.beans.Introspector;
Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));