¿Es mejor marcar '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 de char en Java, siguiendo las mejores prácticas.

Supongamos un escenario simple (obviamente, esto es solo un ejemplo muy simple para dar un significado de práctica a mi pregunta) donde, dada una 's de cadena' como entrada, tienes que contar la número de caracteres numéricos presentes en él.

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?

    
pregunta wyr0 25.11.2015 - 14:36

6 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 los métodos en Character ). Pero el primero no solo ignora la naturaleza Unicode de las cadenas, suponiendo que solo puede haber 0123456789, también también oscurece incluso este razonamiento no válido mediante el uso de códigos de caracteres que solo tienen sentido si sabes algo sobre la historia de codificaciones de caracteres.

    
respondido por el Kilian Foth 25.11.2015 - 14:42
163

Ninguno. Deje que la clase Character incorporada de Java lo resuelva por usted.

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

Hay algunos rangos de caracteres más que los dígitos ASCII que cuentan como dígitos, y ninguno de los ejemplos que publicaste los contará. El JavaDoc para Character.isDigit() enumera estos caracteres rangos como dígitos válidos:

  

Algunos rangos de caracteres Unicode que contienen dígitos:

     
  • '\ u0030' a través de '\ u0039', ISO-LATIN-1 dígitos ('0' a '9')
  •   
  • '\ u0660' a través de '\ u0669', dígitos en árabe-indicador
  •   
  • '\ u06F0' hasta '\ u06F9', dígitos del indicador árabe extendido
  •   
  • '\ u0966' hasta '\ u096F', dígitos de Devanagari
  •   
  • '\ uFF10' a través de '\ uFF19', dígitos de ancho completo
  •   

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

Dicho esto, uno debería delegar a Character.isDigit() incluso con esta lista. A medida que se completen los nuevos planos Unicode, se actualizará el código Java. La actualización de JVM podría hacer que el código antiguo funcione con nuevos caracteres de dígitos sin problemas. También es DRY : al localizar el código "es este un dígito" en un lugar al que se hace referencia en otro lugar, los aspectos negativos de la duplicación de código (es decir, errores) se puede evitar. Finalmente, note 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 a "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 un flujo y solo los dígitos ASCII (a diferencia de otros dígitos) tienen un significado especial, entonces no sería apropiado usar Character.isDigit() .

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

    
respondido por el user22815 25.11.2015 - 14:43
27

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

Sobre el uso de isDigit() : depende. ¿Estás escribiendo un analizador JSON? Solo se aceptan 0 a 9 como dígitos, así que no use isDigit() , verifique >= '0' y <= '9' . ¿Está procesando la entrada del usuario? Use isDigit() mientras el resto de su código realmente pueda manejar la cadena y convertirla en un número correctamente.

    
respondido por el gnasher729 25.11.2015 - 18:23
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.

Debes distinguir entre la comprobación de un carácter específico o la comprobación de un rango o clase de caracteres.

1) Buscando un personaje específico.

Para caracteres comunes, use el literal de carácter, por ejemplo, if(ch=='z')... . Si verifica contra caracteres especiales como tabulaciones o saltos de línea, debe usar los escapes, como if (ch=='\n')... . Si el carácter que está verificando es inusual (por ejemplo, no es reconocible inmediatamente o no está disponible en un teclado estándar), puede usar un código de carácter hexadecimal en lugar del carácter literal. Pero como un código hexadecimal es un "valor mágico", lo extraerías a una constante y lo documentarías:

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) Buscando una clase o rango de caracteres

Realmente no deberías estar haciendo esto directamente en el código de la aplicación, pero deberías encapsularlo en una clase separada que solo tenga que ver con la clasificación de caracteres. Y debería 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 cree, al menos si considera caracteres fuera del rango ASCII.

Si solo le preocupan los caracteres en el rango ASCII, puede 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 hace referencia a los valores de los caracteres y los rangos que usan hexadecimales, ya que así se especifican en el estándar de Unicode.

    
respondido por el JacquesB 26.11.2015 - 09:49
-4

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

    
respondido por el Prem Patel 02.12.2015 - 04:52
-5

Expresiones regulares ( RegEx s) tienen una clase de caracteres específica para dígitos - \d - que se pueden usar para eliminar cualquier otro personaje de tu 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 s son computacionalmente más exigentes que las otras soluciones propuestas, por lo tanto, no deben ser generalmente preferidos .

    
respondido por el Stefano Bragaglia 27.11.2015 - 16:02

Lea otras preguntas en las etiquetas