¿Es mejor verificar `c> = '0'` o` c> = 48`?

46

Después de una discusión con algunos de mis colegas, tengo una pregunta 'filosófica' sobre cómo tratar el tipo de datos char en Java, siguiendo las mejores prácticas.

Supongamos un escenario simple (obviamente, este es solo un ejemplo muy simple para dar un significado práctico a mi pregunta) donde, dada una 's' de cadena como entrada, debe contar la cantidad de caracteres numéricos presentes en ella.

Estas son las 2 posibles soluciones:

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

¿Cuál de los dos es más "limpio" y cumple con las mejores prácticas de Java?

wyr0
fuente
141
¿Por qué escribirías 48 y 57 cuando realmente quieres decir '0' y '9'? Solo escribe lo que quieres decir.
Brandin
99
Espera, ¿qué estás haciendo? Java tiene las VK_constantes que se supone que debes usar; en segundo lugar, usar códigos char es mejor que char Java es un lenguaje de tipo seguro que no debes hacer una verificación de tipos cruzados. @Brandin Se llama prácticas de codificación
Martin Barker
12
Sin molestarse en hacer más que juzgar a las 6 personas que pensaron que esta es una buena pregunta. ¿Estás usando caracteres como números? Si es así, usa números. ¿Lo estás usando como letras? Si es así, usa letras.
Alec Teal
17
@MartinBarker Las VK_*constantes corresponden a claves, no a caracteres .
CodesInChaos
2
Me tomó unos minutos determinar qué hace este código en relación con su pregunta. Ya no está claro porque supone que sé en (1) que sé que este es el rango de dígitos de ISO-Latin 1. Por lo tanto, esto lo hace problemático desde el punto de vista del mantenimiento.
CyberSkull

Respuestas:

124

Ambos son horribles, pero el primero es más horrible.

Ambos ignoran la capacidad incorporada de Java para decidir qué caracteres son "numéricos" (a través de métodos en Character). Pero el primero no solo ignora la naturaleza Unicode de las cadenas, suponiendo que solo puede haber 0123456789, sino que también oculta incluso este razonamiento no válido al usar códigos de caracteres que tienen sentido solo si conoce algo sobre el historial de codificaciones de caracteres.

Kilian Foth
fuente
33
¿Por qué asume que no rechazar los dígitos que no son ASCII es incorrecto? Eso depende del contexto.
CodesInChaos
21
@CodesInChaos Si realmente desea encontrar caracteres numéricos , la exploración de 0123456789 es simplemente incorrecta. Si realmente desea escanear solo estos diez caracteres, entonces son esencialmente tokens sin sentido que solo resultan accidentalmente familiares para personas que solo conocen ASCII / ISO-Latin. No hay nada de malo en eso: a menudo tengo que hacer precisamente eso, por ejemplo, para interactuar con software heredado que realmente solo acepta esos diez caracteres. Pero entonces debes dejar en claro tus intenciones usando algo como matches("[0-9]+"), en lugar de explotar el truco de rango históricamente motivado.
Kilian Foth
15
Hay dígitos de ancho completo , que se parecen a los dígitos ASCII, y en general se requiere una gran cantidad de software para aceptarlos en lugar de los dígitos ASCII. (Obviamente, un montón de software está roto, dependiendo de la definición de "mucho". Puede darse cuenta fácilmente porque los proveedores de software en un país encuentran imposible vender a otro país porque los proveedores no cumplen con los requisitos de los demás países. )
rwong
37
I have a Japanese IME installed , and accidentally type in full - width all the time.
BlueRaja - Danny Pflughoeft
14
"Ambos son horribles", pero olvidaste decir la solución correcta ;-)
Kromster dice que apoya a Mónica el
163

Ninguno. Deje que la clase de caracteres incorporada de Java lo descubra por usted.

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

Hay unos pocos rangos de caracteres más que los dígitos ASCII que cuentan como dígitos, y ninguno de los ejemplos que publicó los contará. El JavaDoc para Character.isDigit()listas de estos rangos de caracteres como siendo dígitos válidos:

Algunos rangos de caracteres Unicode que contienen dígitos:

  • '\ u0030' a '\ u0039', dígitos ISO-LATIN-1 ('0' a '9')
  • '\ u0660' a '\ u0669', dígitos árabe-índicos
  • '\ u06F0' a '\ u06F9', dígitos árabes-índicos extendidos
  • '\ u0966' hasta '\ u096F', dígitos de Devanagari
  • '\ uFF10' a '\ uFF19', dígitos de ancho completo

Muchos otros rangos de caracteres también contienen dígitos.

Dicho esto, uno debería delegar Character.isDigit()incluso con esta lista. A medida que se llenen nuevos planos Unicode, se actualizará el código Java. Actualizar la JVM podría hacer que el código antiguo funcione con nuevos caracteres de dígitos sin problemas. También es SECO : al localizar el código "es un dígito" en un lugar al que se hace referencia en otro lugar, se pueden evitar los aspectos negativos de la duplicación de código (es decir, errores). Finalmente, observe la última línea: esta lista no es exhaustiva y hay otros dígitos.

Personalmente, preferiría delegar en las bibliotecas principales de Java y dedicar mi tiempo a tareas más productivas que "calcular qué es un dígito".


La única excepción a esta regla es si realmente necesita probar los dígitos ASCII literales y no otros dígitos. Por ejemplo, si está analizando una secuencia y solo los dígitos ASCII (a diferencia de otros dígitos) tienen un significado especial, entonces no sería apropiado usarlos Character.isDigit().

En ese caso, escribiría otro método, por ejemplo, MyClass.isAsciiDigit()y pondría la lógica allí. Obtiene los mismos beneficios de la reutilización de código, el nombre es muy claro en cuanto a lo que está comprobando y la lógica es correcta.


fuente
44
Gran respuesta para proporcionar realmente el código limpio que hace el truco.
Pierre Arlaud
27

Si alguna vez escribe una aplicación en C que usa EBCDIC como el conjunto de caracteres básico y necesita procesar caracteres ASCII, use 48y 57. Estas haciendo eso? No lo creo.

Sobre el uso isDigit(): depende. ¿Estás escribiendo un analizador JSON? Solo 0para 9ser aceptados como dígitos, así que no use isDigit(), verifique >= '0'y <= '9'. ¿Estás procesando la entrada del usuario? Úselo isDigit()siempre que el resto del código pueda manejar la cadena y convertirla en un número correctamente.

gnasher729
fuente
3
En realidad, puede escribir aplicaciones en Java que obtienen y devuelven EBCDIC. Esto no es divertido.
Thorbjørn Ravn Andersen
Similar 'no divertido' estaba pasando por el código que se escribió usando los valores decimales de los caracteres EBCDIC al convertirlo en un entorno multiplataforma ...
Gwyn Evans
1
Si está procesando datos EBCDIC en Java, probablemente debería convertirlos al juego de caracteres UTF-16 nativo de Java antes de procesarlos como caracteres. Pero supongo que eso realmente depende de la aplicación; Con suerte, si su programa tiene que lidiar con EBCDIC, entonces comprenderá lo que debe hacerse.
Michael Burr
1
El punto principal es que para procesar EBCDIC en Java, tanto '0' como 48 son incorrectos al detectar un dígito cero. Más actual, en C, C ++, etc. '\ n' y '\ r' son definiciones de implementación, por lo que si desea detectar un par CR / LF de Windows en un archivo utilizando un compilador que no sea Windows, compruebe mejor los valores decimales en lugar de buscando '\ n' y '\ r'.
gnasher729
12

El segundo ejemplo es claramente superior. El significado del segundo ejemplo es inmediatamente obvio cuando miras el código. El significado del primer ejemplo solo es obvio si ha memorizado toda la tabla ASCII en su cabeza.

Debe distinguir entre verificar un carácter específico o verificar un rango o clase de caracteres.

1) Verificación de un personaje específico.

Para los caracteres ordinarios, utilice el carácter literal, por ejemplo, if(ch=='z').... Si comprueba caracteres especiales como tabulación o salto de línea, debe usar los escapes, como if (ch=='\n').... Si el carácter que está buscando es inusual (por ejemplo, no se reconoce inmediatamente o no está disponible en un teclado estándar), puede usar un código de caracteres hexadecimales en lugar del carácter literal. Pero como un código hexadecimal es un "valor mágico", lo extraería a una constante y lo documentaría:

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

Los códigos hexadecimales son la forma estándar de especificar códigos de caracteres.

2) Verificación de una clase o rango de personajes

Realmente no debería estar haciendo esto directamente en el código de la aplicación, sino que debería encapsularlo en una clase separada solo relacionada con la clasificación de caracteres. Y debe variar esto, ya que las bibliotecas ya existen para este propósito, y la clasificación de caracteres suele ser más compleja de lo que piensa, al menos si considera caracteres fuera del rango ASCII.

Si solo le preocupan los caracteres en el rango ASCII, podría usar literales de caracteres en esta biblioteca, de lo contrario probablemente usaría literales hexadecimales. Si observa el código fuente de la biblioteca de caracteres incorporada de Java, también se refiere a valores de caracteres y rangos utilizando hexadecimal, ya que así es como se especifican en el estándar Unicode.

JacquesB
fuente
1
También recomendaría escribir el carácter literal en hexadecimal utilizando '\x2603'para ser explícito que está probando el valor de un carácter con una codificación hexadecimal y no cualquier número aleatorio.
wefwefa3
-4

Siempre es mejor usarlo c >= '0'porque c >= 48necesitas convertir c en código ascii.

Prem Patel
fuente
3
¿Qué dice esta respuesta que no se dijo ya en las respuestas anteriores de hace una semana?
-5

Las expresiones regulares ( RegEx s) tienen una clase de caracteres específica para los dígitos \d, que puede usarse para eliminar cualquier otro carácter de su cadena. La longitud de la cadena resultante es el valor deseado.

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

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

Sin embargo, tenga en cuenta que los RegEx son computacionalmente más exigentes que las otras soluciones propuestas, por lo tanto, no deberían preferirse en general .

Stefano Bragaglia
fuente
¡Una forma muy elegante de hacer el cheque!
Kevin Robatel
Las expresiones regulares son excesivas para una tarea como esta
Pharap
2
@StefanoBragaglia Después de volver a leer su respuesta, creo que realmente no responde la pregunta.
Pharap
2
Su respuesta proporciona una forma diferente de resolver el problema de "cómo cuento los dígitos en una cadena". No responde al problema subyacente con las muestras de código y la representación de las constantes, ya sea como números o caracteres.
2
En realidad, esto no cuenta los dígitos (solo le dice cuál es la longitud de la cadena después de haber eliminado todos los dígitos, que no está ni aquí ni allá), pero estoy de acuerdo en que en realidad no responde la pregunta. Como, por ejemplo, nadie preguntaba por eliminar caracteres de las cadenas. La pregunta es solo acerca de la forma adecuada de las mejores prácticas para verificar si el carácter es numérico.
doppelgreener