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

46

Após uma discussão com alguns colegas, tenho uma pergunta '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, a fim de dar um significado prático à minha pergunta) , em que, dado um String 's' como entrada, você deve contar o número de caracteres numéricos presentes nele.

Estas são as 2 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 melhores práticas de Java?

wyr0
fonte
141
Por que você escreveria 48 e 57 quando na verdade quer dizer '0' e '9'? Basta escrever o que você quer dizer.
Brandin
9
Espere o que você está fazendo, Java tem as VK_constantes que você deveria usar; em segundo lugar, usar códigos char é melhor que char. Java é uma linguagem de tipo seguro que você não deve fazer verificação de tipo cruzado. @Brandin chamadas práticas de TI da codificação
Martin Barker
12
Sem se preocupar em fazer mais do que julgar as 6 pessoas que pensam que esta é uma boa pergunta. Você está usando caracteres como números? Se sim, use números. Você está usando isso como letras? Se sim, use letras.
Alec Teal
17
@MartinBarker As VK_*constantes correspondem a chaves, não a caracteres .
CodesInChaos
2
Levei alguns minutos para determinar o que esse código faz em relação à sua pergunta. Já não está claro porque pressupõe que eu sei em (1) que sei que esse é o intervalo de dígitos da ISO-Latin 1. Portanto, isso torna problemático do ponto de vista de manutenção.
CyberSkull

Respostas:

124

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

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

Kilian Foth
fonte
33
Por que você está assumindo que dígitos não-ASCII não rejeitados estão errados? Isso depende do contexto.
CodesInChaos
21
@CodesInChaos Se você realmente deseja encontrar caracteres numéricos , a busca por 0123456789 está totalmente errada. Se você realmente deseja procurar apenas esses dez caracteres, eles são essencialmente tokens sem sentido que, acidentalmente, parecem familiares para pessoas que conhecem apenas ASCII / ISO-Latin. Não há nada de errado com isso - muitas vezes tenho que fazer exatamente isso, por exemplo, para interagir com software legado que realmente aceita apenas esses dez caracteres. Mas, então, você deve esclarecer suas intenções usando algo como matches("[0-9]+"), em vez de explorar o truque de alcance historicamente motivado.
Kilian Foth
15
Existem dígitos de largura total , que se parecem com os dígitos ASCII e, em geral, é necessário muito software para aceitá-los no lugar dos dígitos ASCII. (Obviamente, muitos softwares estão com problemas, dependendo da definição de "muitos". É fácil perceber porque os fornecedores de software em um país acham impossível vender para outro país, porque não respeitam os requisitos de outros países. )
rwong 25/11
37
I have a Japanese IME , and accidentally type in full - width all the..
BlueRaja - Danny Pflughoeft
14
"Ambos são horríveis", mas você esqueceu de dizer a solução certa ;-)
Kromster diz apoio Monica
163

Nem. Deixe a classe de caracteres incorporada do Java descobrir por você.

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

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

Alguns intervalos de caracteres Unicode que contêm dígitos:

  • '\ u0030' a '\ u0039', dígitos ISO-LATIN-1 ('0' a '9')
  • '\ u0660' a '\ u0669', dígitos que indicam árabe
  • '\ u06F0' a '\ u06F9', dígitos em árabe-indicador estendido
  • '\ u0966' a '\ u096F', dígitos do Devanagari
  • '\ uFF10' a '\ uFF19', dígitos de largura total

Muitos outros intervalos de caracteres também contêm dígitos.

Dito isto, deve-se delegar para Character.isDigit()mesmo com esta lista. À medida que novos planos Unicode são preenchidos, o código Java será atualizado. Atualizar a JVM poderia fazer com que o código antigo funcionasse perfeitamente com novos caracteres de dígito. Também é SECO : localizando o código "é um dígito" em um local referenciado em outro lugar, os aspectos negativos da duplicação de código (ou seja, bugs) podem ser evitados. Por fim, observe a última linha: esta lista não é exaustiva e há outros dígitos.

Pessoalmente, prefiro delegar nas 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 apenas dígitos ASCII (em oposição a outros dígitos) tiverem um significado especial, não seria apropriado usá-lo Character.isDigit().

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


fonte
4
Ótima resposta para realmente fornecer o código limpo que faz o truque.
Pierre Arlaud
27

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

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

gnasher729
fonte
3
Na verdade, você pode escrever aplicativos em Java que obtêm e retornam EBCDIC. Isso não é divertido.
Thorbjørn Ravn Andersen
Semelhante 'não é divertido' estava passando por código que foi escrito usando os valores decimais dos caracteres EBCDIC quando convertê-la em um ambiente multi-plataforma ...
Gwyn Evans
1
Se você estiver processando dados EBCDIC em Java, provavelmente deverá convertê-los no charset UTF-16 nativo em Java antes de processá-los como caracteres. Mas acho que isso realmente depende da aplicação; espero que, se seu programa tiver que lidar com o EBCDIC, você entenderá o que precisa ser feito.
Michael Burr
1
O ponto principal é que, para o processamento do EBCDIC em Java, ambos '0' e 48 estão errados ao detectar um dígito zero. Mais atual, em C, C ++ etc. '\ n' e '\ r' são definidos de implementação; portanto, se você deseja detectar um par CR / LF do Windows em um arquivo usando um compilador que não seja o Windows, verifique melhor os valores decimais em vez de verificando '\ n' e '\ r'.
gnasher729
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 é óbvio apenas se você memorizou toda a tabela ASCII em 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 literal, por exemplo if(ch=='z')...,. Se você verificar caracteres especiais, como tabulação ou quebra de linha, use os escapes, como if (ch=='\n').... Se o caractere que você está procurando é incomum (por exemplo, não é reconhecível imediatamente ou não está 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 extrairá para uma constante e o documentará:

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

Os códigos hexadecimais são a maneira padrão de especificar códigos de caracteres.

2) Verificando uma classe ou intervalo de caracteres

Você realmente não deveria fazer isso diretamente no código do aplicativo, mas deveria encapsular em uma classe separada, apenas relacionada à classificação de caracteres. E você deve variar disso, já que as bibliotecas já existem para esse fim, 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ê estiver preocupado apenas com caracteres no intervalo ASCII, poderá usar literais de caracteres nesta biblioteca; caso contrário, provavelmente usaria literais hexadecimais. Se você olhar para o código-fonte da biblioteca de caracteres interna Java, ele também se refere a valores e intervalos de caracteres usando hexadecimal, pois é assim que eles são especificados no padrão Unicode.

JacquesB
fonte
1
Eu também recomendaria escrever o literal do caractere em hexadecimal usando, em '\x2603'vez disso, para ser explícito que você está testando o valor de um caractere com uma codificação hexadecimal e não apenas qualquer número aleatório.
wefwefa3
-4

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

Prem Patel
fonte
3
O que essa resposta afirma que ainda não foi dita nas respostas anteriores de uma semana atrás?
-5

Expressões regulares ( RegEx ) têm uma classe de caracteres específica para dígitos - \d- que podem ser usados ​​para remover qualquer outro caractere da sua string. O comprimento da sequência resultante é o valor desejado.

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

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

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

Stefano Bragaglia
fonte
Maneira muito elegante de fazer a verificação!
Kevin Robatel
Regexes são um exagero para uma tarefa como esta
Pharap
2
@StefanoBragaglia Depois de reler sua resposta, acho que realmente não responde à pergunta.
Pharap
2
Sua resposta fornece uma maneira diferente de resolver o problema de "como conto dígitos em uma string". Ele não responde ao problema subjacente com os exemplos de código e a representação das constantes - como números ou caracteres.
2
Na verdade, isso não conta os dígitos (apenas informa qual é o comprimento da string depois que você removeu todos os dígitos, que não estão aqui nem ali), mas eu concordo que na verdade não responde à pergunta. Como, por exemplo, ninguém estava perguntando sobre remover caracteres de strings. A pergunta está apenas perguntando sobre a maneira apropriada de melhor prática para verificar se o caractere é numérico.
doppelgreener