É melhor verificar 'c =' 0 '' ou 'c = 48'?

46

Depois de uma discussão com alguns colegas, tenho uma questão "filosófica" sobre como tratar o tipo de dados char em Java, seguindo as melhores práticas.

Suponha um cenário simples (obviamente este é apenas um exemplo muito simples para dar um significado prático à minha pergunta) onde, dado um String 's' como entrada, você tem que contar o número de caracteres numéricos presentes.

Estas são as duas soluções possíveis:

1)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= 48 && s.charAt(i) <= 57) {
            n++;
        }
    }

2)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= '0' && s.charAt(i) <= '9' ) {
            n++;
        }
    }

Qual dos dois é mais 'limpo' e compatível com as práticas recomendadas do Java?

    
por wyr0 25.11.2015 / 14:36
fonte

6 respostas

124

Ambos são horríveis, mas o primeiro é mais horrível.

Ambos ignoram o recurso interno do Java para decidir quais caracteres são "numéricos" (por meio de métodos em Character ). Mas o primeiro não apenas ignora a natureza Unicode das strings, assumindo que só pode haver 0123456789, mas também obscurece até mesmo esse raciocínio inválido usando códigos de caracteres que só fazem sentido se você souber algo sobre o histórico de codificações de caracteres.

    
por 25.11.2015 / 14:42
fonte
163

Nem Deixe que a classe Personagem incorporada do Java a descubra para você.

for (int i = 0; i < s.length(); ++i) {
  if (Character.isDigit(s.charAt(i))) {
    ++n;
  }
}

Existem mais alguns intervalos de caracteres que os dígitos ASCII que contam como dígitos, e nenhum dos exemplos que você postou contará. O JavaDoc para Character.isDigit() lista esses caracteres intervalos como sendo dígitos válidos:

Some Unicode character ranges that contain digits:

  • '\u0030' through '\u0039', ISO-LATIN-1 digits ('0' through '9')
  • '\u0660' through '\u0669', Arabic-Indic digits
  • '\u06F0' through '\u06F9', Extended Arabic-Indic digits
  • '\u0966' through '\u096F', Devanagari digits
  • '\uFF10' through '\uFF19', Fullwidth digits

Many other character ranges contain digits as well.

Dito isso, deve-se delegar para Character.isDigit() mesmo com essa lista. Conforme novos planos Unicode são preenchidos, o código Java será atualizado. A atualização da JVM pode fazer com que o código antigo funcione com novos caracteres de dígitos perfeitamente. Também é DRY : localizando o código "é um dígito" em um lugar referenciado em outro lugar, os aspectos negativos de duplicação de código (ou seja, bugs) podem ser evitados. Por fim, anote a última linha: essa lista não é completa e há outros dígitos.

Pessoalmente, prefiro delegar às principais bibliotecas Java e gastar meu tempo em tarefas mais produtivas do que "descobrir o que é um dígito".

A única exceção a essa regra é se você realmente precisa testar os dígitos ASCII literais e não outros dígitos. Por exemplo, se você estiver analisando um fluxo e somente dígitos ASCII (em oposição a outros dígitos) tiverem um significado especial, então não será apropriado usar Character.isDigit() .

Nesse caso, eu escreveria outro método, por exemplo MyClass.isAsciiDigit() e coloque a lógica lá. Você obtém os mesmos benefícios de reutilização de código, o nome é super claro quanto ao que está verificando e a lógica está correta.

    
por 25.11.2015 / 14:43
fonte
27

Se você já escreveu um aplicativo em C que usa EBCDIC como o conjunto de caracteres básico e precisa processar caracteres ASCII, use 48 e 57 . Você está fazendo isso? Acho que não.

Sobre o uso de isDigit() : depende. Você está escrevendo um analisador JSON? Somente 0 to 9 são aceitos como dígitos, portanto, não use isDigit() , verifique >= '0' e <= '9' . Você está processando a entrada do usuário? Use isDigit() desde que o resto do seu código realmente possa manipular a string e transformá-la em um número corretamente.

    
por 25.11.2015 / 18:23
fonte
12

O segundo exemplo é claramente superior. O significado do segundo exemplo é imediatamente óbvio quando você olha para o código. O significado do primeiro exemplo é apenas óbvio se você memorizou toda a tabela ASCII na sua cabeça.

Você deve distinguir entre a verificação de um caractere específico ou a verificação de um intervalo ou classe de caracteres.

1) Verificando um caractere específico.

Para caracteres comuns, use o literal do caractere, por exemplo, if(ch=='z')... . Se você verificar caracteres especiais, como tabulação ou quebra de linha, você deve usar as fugas, como if (ch=='\n')... . Se o caractere que você está verificando for incomum (por exemplo, não imediatamente reconhecível ou não disponível em um teclado padrão), você pode usar um código de caractere hexadecimal em vez do caractere literal. Mas como um código hexadecimal é um "valor mágico", você o extrai para uma constante e o documenta:

const char snowman = 0x2603; // snowman char used to detect encoding issues
...
if (ch==showman)...

Códigos hexadecimais são o modo padrão de especificar códigos de caracteres.

2) Verificando uma classe ou intervalo de caracteres

Você realmente não deveria estar fazendo isso diretamente no código do aplicativo, mas deveria encapsulá-lo em uma classe separada, preocupada apenas com a classificação de caracteres. E você deve variar disso, já que as bibliotecas já existem para esse propósito, e a classificação de caracteres é geralmente mais complexa do que você pensa, pelo menos se você considerar caracteres fora do intervalo ASCII.

Se você está preocupado apenas com os caracteres no intervalo ASCII, você poderia usar literais de caracteres nesta biblioteca, caso contrário, você provavelmente usaria literais hexadecimais. Se você observar o código-fonte da biblioteca de caracteres incorporada em Java, ele também se referirá a valores e intervalos de caracteres usando hexadecimal, pois é assim que eles são especificados no padrão Unicode.

    
por 26.11.2015 / 09:49
fonte
-4

É sempre melhor usar c >= '0' , porque para c >= 48 você precisa converter c no código ascii.

    
por 02.12.2015 / 04:52
fonte
-5

Expressões Regulares ( RegEx s) têm uma classe de caracteres específica para dígitos - \d - que podem ser usadas para remover qualquer outra personagem da sua string. O comprimento da string resultante é o valor desejado.

public static int countDigits(String str) {
    str = Objects.requireNonNull(str).trim();

    return str.replaceAll("[^\d]", "").length();
}

Observe, no entanto, que RegEx s são computacionalmente mais exigentes do que as outras soluções propostas, portanto, elas não devem ser geralmente preferidas .

    
por 27.11.2015 / 16:02
fonte