Maneira mais eficiente de tornar o primeiro caractere de uma String minúsculo?

97

Qual é a maneira mais eficiente de fazer o primeiro caractere de uma Stringcaixa baixa?

Posso pensar em várias maneiras de fazer isso:

Usando charAt()comsubstring()

String input   = "SomeInputString";
String output  = Character.toLowerCase(input.charAt(0)) +
                   (input.length() > 1 ? input.substring(1) : "");

Ou usando um chararray

 String input  = "SomeInputString";
 char c[]      = input.toCharArray();
 c[0]          = Character.toLowerCase(c[0]);
 String output = new String(c);

Tenho certeza de que existem muitas outras maneiras excelentes de se conseguir isso. O que você recomenda?

Andy
fonte
A melhor maneira seria alterar seus requisitos, se possível. Aceite um StringBuilder em vez de uma String e você pode modificá-lo diretamente.
Mark Peters
Bem, isso não é uma resposta porque está fora do Java e depende da codificação ASCII e de saber que o caractere já é alfabético. É um hack dos antigos:c[0] |= ' ';
Mike Dunlavey,
possível duplicata de Convertendo para maiúsculas e minúsculas em Java
Raedwald
essa é uma pergunta diferente
Andy

Respostas:

123

Testei as abordagens promissoras usando JMH . Código de referência completo .

Suposição durante os testes (para evitar a verificação de casos extremos sempre): o comprimento da string de entrada é sempre maior que 1.

Resultados

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

A pontuação é operações por segundo, quanto mais, melhor.

Testes

  1. test1 foi a primeira abordagem de Andy e Hllink:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2foi a abordagem do segundo Andy. Também é Introspector.decapitalize()sugerido por Daniel, mas sem duas ifafirmações. O primeiro iffoi removido devido à suposição de teste. O segundo foi removido, porque estava violando a correção (ou seja, a entrada "HI"voltaria "HI"). Isso foi quase o mais rápido.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
    
  3. test3foi uma modificação de test2, mas em vez de Character.toLowerCase(), eu estava adicionando 32, que funciona corretamente se e somente se a string estiver em ASCII. Este foi o mais rápido. c[0] |= ' 'do comentário de Mike deu o mesmo desempenho.

    char c[] = string.toCharArray();
    c[0] += 32;
    string = new String(c);
    
  4. test4usado StringBuilder.

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
    
  5. test5usou duas substring()chamadas.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6usa reflexão para mudar char value[]diretamente na String. Este foi o mais lento.

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

Conclusões

Se o comprimento da string for sempre maior que 0, use test2.

Caso contrário, temos que verificar os casos mais importantes:

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

Se você tiver certeza de que seu texto estará sempre em ASCII e estiver procurando por desempenho extremo porque encontrou esse código no gargalo, use test3.

Adam Stelmaszczyk
fonte
95

Eu encontrei uma boa alternativa se você não quiser usar uma biblioteca de terceiros:

import java.beans.Introspector;

Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));
Daniel Pacak
fonte
14
Do documento para este método: "Isso normalmente significa converter o primeiro caractere de maiúscula em minúscula, mas no caso especial (incomum), quando há mais de um caractere e o primeiro e o segundo caracteres são maiúsculas, deixamos sozinho. "
Andy
1
Além disso, olhando para a fonte, uma vez que esse método lida com o caso especial que descrevi no comentário anterior, ele apenas usa a matriz char como mencionei em minha pergunta.
Andy
2
Exatamente o que eu precisava. Introspector.decapitalize ("ABC") ainda será ABC. WordUtils.uncapitalize ("ABC") produz "aBC". Apenas compartilhando o primeiro é como o Spring faz a nomeação automática de beans, então se você precisar recuperar ABCService pelo nome do bean, não é aBCService, mas ABCService ainda.
aldeão
21

Quando se trata de manipulação de strings, dê uma olhada em Jakarta Commons Lang StringUtils .

Carlos Tasada
fonte
8
Mais especificamente, o método uncapitalize (java.lang.String) Usando StringUtils tem a vantagem adicional de não ter que se preocupar com NullPointerExceptions em seu código.
hexium
3
Não necessariamente o mais eficiente, mas talvez o mais claro, o que conta muito.
David Gelhar
2
Depende de qual recurso você está tornando mais eficiente - tempo da CPU ou do programador :)
Dan Gravell
15

Se você deseja usar o Apache Commons, pode fazer o seguinte:

import org.apache.commons.lang3.text.WordUtils;
[...] 
String s = "SomeString"; 
String firstLower = WordUtils.uncapitalize(s);

Resultado: someString

Sebastian
fonte
3
É uma solução boa e limpa, mas está obsoleta agora, devemos usar o texto comum:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
dk7
10

Apesar de uma abordagem orientada para char, eu sugeriria uma solução orientada para String. String.toLowerCase é específico do Locale, então eu levaria esse problema em consideração. String.toLowerCaseé preferível para letras minúsculas de acordo com Character.toLowerCase . Além disso, uma solução orientada a char não é totalmente compatível com Unicode, porque Character.toLowerCase não pode lidar com caracteres suplementares.

public static final String uncapitalize(final String originalStr,
            final Locale locale) {
        final int splitIndex = 1;
        final String result;
        if (originalStr.isEmpty()) {
        result = originalStr;
        } else {
        final String first = originalStr.substring(0, splitIndex).toLowerCase(
                locale);
        final String rest = originalStr.substring(splitIndex);
        final StringBuilder uncapStr = new StringBuilder(first).append(rest);
        result = uncapStr.toString();
        }
        return result;
    }

ATUALIZAÇÃO: como exemplo de quão importante é a configuração da localidade, vamos usar letras minúsculas Iem turco e alemão:

System.out.println(uncapitalize("I", new Locale("TR","tr")));
System.out.println(uncapitalize("I", new Locale("DE","de")));

produzirá dois resultados diferentes:

Eu

Eu

Michael Konietzka
fonte
7

Strings em Java são imutáveis, portanto, de qualquer maneira, uma nova string será criada.

Seu primeiro exemplo provavelmente será um pouco mais eficiente, porque ele só precisa criar uma nova string e não uma matriz temporária de caracteres.

Alan Geleynse
fonte
1
Na verdade, a primeira forma cria uma String temporária (para substring), que é mais cara do que a matriz de caracteres.
Hot Licks
1
Inútil sem dados de apoio
Nitsan Wakart
3

Um método estático muito curto e simples para arquivar o que você deseja:

public static String decapitalizeString(String string) {
    return string == null || string.isEmpty() ? "" : Character.toLowerCase(string.charAt(0)) + string.substring(1);
}
Hllink
fonte
2

Se o que você precisa é muito simples (por exemplo, nomes de classe java, sem localidades), você também pode usar a classe CaseFormat na biblioteca Google Guava .

String converted = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, "FooBar");
assertEquals("fooBar", converted);

Ou você pode preparar e reutilizar um objeto conversor, o que pode ser mais eficiente.

Converter<String, String> converter=
    CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.LOWER_CAMEL);

assertEquals("fooBar", converter.convert("FooBar"));

Para entender melhor a filosofia de manipulação de strings do Google Guava, verifique esta página wiki .

Peter Lamberg
fonte
1
String testString = "SomeInputString";
String firstLetter = testString.substring(0,1).toLowerCase();
String restLetters = testString.substring(1);
String resultString = firstLetter + restLetters;
Bae Cheol Shin
fonte
1

Eu descobri isso apenas hoje. Tentei fazer sozinho da maneira mais pedestre. Isso levou uma linha, embora longa. Aqui vai

String str = "TaxoRank"; 

System.out.println(" Before str = " + str); 

str = str.replaceFirst(str.substring(0,1), str.substring(0,1).toLowerCase());

System.out.println(" After str = " + str);

Dá:

Antes de str = TaxoRanks

Depois de str = taxoRanks

user3501758
fonte
1
val str = "Hello"
s"${str.head.toLower}${str.tail}"

Resultado:

res4: String = hello
Vivek
fonte