Compruebe si una cadena es numérica en Java

1. Introducción

A menudo, mientras operamos con String s, necesitamos averiguar si un String es un número válido o no.

En este tutorial, exploraremos varias formas de detectar si la cadena dada es numérica , primero usando Java simple, luego expresiones regulares y finalmente usando bibliotecas externas.

Una vez que hayamos terminado de discutir varias implementaciones, usaremos puntos de referencia para tener una idea de qué métodos son óptimos.

2. Requisitos previos

Comencemos con algunos requisitos previos antes de pasar al contenido principal.

En la última parte de este artículo, usaremos la biblioteca externa Apache Commons para la cual agregaremos su dependencia en nuestro pom.xml :

 org.apache.commons commons-lang3 3.9 

La última versión de esta biblioteca se puede encontrar en Maven Central.

3. Uso de Java simple

Quizás la forma más fácil y confiable de verificar si una cadena es numérica o no es analizándola utilizando los métodos integrados de Java:

  1. Integer.parseInt (Cadena)
  2. Float.parseFloat (cadena)
  3. Double.parseDouble (Cadena)
  4. Long.parseLong (cadena)
  5. new BigInteger (String)

Si estos métodos no arrojan ninguna excepción NumberFormatException , significa que el análisis fue exitoso y la cadena es numérica:

public static boolean isNumeric(String strNum) { if (strNum == null) { return false; } try { double d = Double.parseDouble(strNum); } catch (NumberFormatException nfe) { return false; } return true; }

Veamos este método en acción:

assertThat(isNumeric("22")).isTrue(); assertThat(isNumeric("5.05")).isTrue(); assertThat(isNumeric("-200")).isTrue(); assertThat(isNumeric("10.0d")).isTrue(); assertThat(isNumeric(" 22 ")).isTrue(); assertThat(isNumeric(null)).isFalse(); assertThat(isNumeric("")).isFalse(); assertThat(isNumeric("abc")).isFalse();

En nuestro método isNumeric () , solo estamos verificando valores que sean de tipo Double , pero este método también se puede modificar para verificar números enteros , flotantes , largos y grandes usando cualquiera de los métodos de análisis que hemos registrado anteriormente. .

Estos métodos también se describen en el artículo Conversiones de cadenas de Java.

4. Usar expresiones regulares

Ahora usemos regex -? \ D + (\. \ D +)? para hacer coincidir las cadenas numéricas que consisten en el entero positivo o negativo y los flotantes.

Pero no hace falta decir que definitivamente podemos modificar esta expresión regular para identificar y manejar una amplia gama de reglas. Aquí, lo haremos simple.

Analicemos esta expresión regular y veamos cómo funciona:

  • -? - esta parte identifica si el número dado es negativo, el guión " - " busca el guión literalmente y el signo de interrogación " ? ”Marca su presencia como opcional
  • \ d + : busca uno o más dígitos
  • (\. \ d +)? - esta parte de la expresión regular es para identificar números flotantes. Aquí buscamos uno o más dígitos seguidos de un punto. El signo de interrogación, al final, significa que este grupo completo es opcional.

Las expresiones regulares son un tema muy amplio. Para obtener una breve descripción general, consulte nuestro tutorial sobre la API de expresiones regulares de Java.

Por ahora, creemos un método usando la expresión regular anterior:

private Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?"); public boolean isNumeric(String strNum) { if (strNum == null) { return false; } return pattern.matcher(strNum).matches(); }

Veamos ahora algunas afirmaciones para el método anterior:

assertThat(isNumeric("22")).isTrue(); assertThat(isNumeric("5.05")).isTrue(); assertThat(isNumeric("-200")).isTrue(); assertThat(isNumeric(null)).isFalse(); assertThat(isNumeric("abc")).isFalse();

5. Usando Apache Commons

En esta sección, discutiremos varios métodos disponibles en la biblioteca Apache Commons.

5.1. NumberUtils.isCreatable (cadena)

NumberUtils de Apache Commons proporciona un método estático NumberUtils.isCreatable (String) que comprueba si un String es un número Java válido o no.

Este método acepta:

  1. Números hexadecimales que comienzan con 0x o 0X
  2. Números octales que comienzan con un 0 inicial
  3. Notación científica (por ejemplo, 1.05e-10)
  4. Números marcados con un calificador de tipo (por ejemplo, 1L o 2.2d)

Si la cadena proporcionada es nula o está vacía , no se considera un número y el método devolverá falso .

Realicemos algunas pruebas usando este método:

assertThat(NumberUtils.isCreatable("22")).isTrue(); assertThat(NumberUtils.isCreatable("5.05")).isTrue(); assertThat(NumberUtils.isCreatable("-200")).isTrue(); assertThat(NumberUtils.isCreatable("10.0d")).isTrue(); assertThat(NumberUtils.isCreatable("1000L")).isTrue(); assertThat(NumberUtils.isCreatable("0xFF")).isTrue(); assertThat(NumberUtils.isCreatable("07")).isTrue(); assertThat(NumberUtils.isCreatable("2.99e+8")).isTrue(); assertThat(NumberUtils.isCreatable(null)).isFalse(); assertThat(NumberUtils.isCreatable("")).isFalse(); assertThat(NumberUtils.isCreatable("abc")).isFalse(); assertThat(NumberUtils.isCreatable(" 22 ")).isFalse(); assertThat(NumberUtils.isCreatable("09")).isFalse();

Observe cómo obtenemos afirmaciones verdaderas para números hexadecimales, números octales y notaciones científicas en las líneas 6, 7 y 8 respectivamente.

Además, en la línea 14, la cadena "09" devuelve falso porque el "0" anterior indica que se trata de un número octal y "09" no es un número octal válido.

Para cada entrada que devuelva verdadero con este método, podemos usar NumberUtils.createNumber (String) que nos dará el número válido.

5.2. NumberUtils.isParsable (cadena)

El método NumberUtils.isParsable (String) comprueba si la cadena dada se puede analizar o no.

Parsable numbers are those that are parsed successfully by any parse method like Integer.parseInt(String), Long.parseLong(String), Float.parseFloat(String) or Double.parseDouble(String).

Unlike NumberUtils.isCreatable(), this method won't accept hexadecimal numbers, scientific notations or strings ending with any type qualifier, that is, ‘f', ‘F', ‘d' ,'D' ,'l'or‘L'.

Let's look at some affirmations:

assertThat(NumberUtils.isParsable("22")).isTrue(); assertThat(NumberUtils.isParsable("-23")).isTrue(); assertThat(NumberUtils.isParsable("2.2")).isTrue(); assertThat(NumberUtils.isParsable("09")).isTrue(); assertThat(NumberUtils.isParsable(null)).isFalse(); assertThat(NumberUtils.isParsable("")).isFalse(); assertThat(NumberUtils.isParsable("6.2f")).isFalse(); assertThat(NumberUtils.isParsable("9.8d")).isFalse(); assertThat(NumberUtils.isParsable("22L")).isFalse(); assertThat(NumberUtils.isParsable("0xFF")).isFalse(); assertThat(NumberUtils.isParsable("2.99e+8")).isFalse();

On line 4, unlike NumberUtils.isCreatable(), the number starting with string “0” isn't considered as an octal number, but a normal decimal number and hence it returns true.

We can use this method as a replacement for what we did in section 3, where we’re trying to parse a number and checking for an error.

5.3. StringUtils.isNumeric(CharSequence)

The method StringUtils.isNumeric(CharSequence) checks strictly for Unicode digits. This means:

  1. Any digits from any language that is a Unicode digit is acceptable
  2. Since a decimal point is not considered as a Unicode digit, it's not valid
  3. Leading signs (either positive or negative) are also not acceptable

Let's now see this method in action:

assertThat(StringUtils.isNumeric("123")).isTrue(); assertThat(StringUtils.isNumeric("١٢٣")).isTrue(); assertThat(StringUtils.isNumeric("१२३")).isTrue(); assertThat(StringUtils.isNumeric(null)).isFalse(); assertThat(StringUtils.isNumeric("")).isFalse(); assertThat(StringUtils.isNumeric(" ")).isFalse(); assertThat(StringUtils.isNumeric("12 3")).isFalse(); assertThat(StringUtils.isNumeric("ab2c")).isFalse(); assertThat(StringUtils.isNumeric("12.3")).isFalse(); assertThat(StringUtils.isNumeric("-123")).isFalse();

Note that the input parameters in lines 2 and 3 are representing numbers 123 in Arabic and Devanagari respectively. Since they're valid Unicode digits, this method returns true on them.

5.4. StringUtils.isNumericSpace(CharSequence)

The StringUtils.isNumericSpace(CharSequence) checks strictly for Unicode digits and/or space. This is same as StringUtils.isNumeric() with the only difference being that it accepts spaces as well, not only leading and trailing spaces but also if they're in between numbers:

assertThat(StringUtils.isNumericSpace("123")).isTrue(); assertThat(StringUtils.isNumericSpace("١٢٣")).isTrue(); assertThat(StringUtils.isNumericSpace("")).isTrue(); assertThat(StringUtils.isNumericSpace(" ")).isTrue(); assertThat(StringUtils.isNumericSpace("12 3")).isTrue(); assertThat(StringUtils.isNumericSpace(null)).isFalse(); assertThat(StringUtils.isNumericSpace("ab2c")).isFalse(); assertThat(StringUtils.isNumericSpace("12.3")).isFalse(); assertThat(StringUtils.isNumericSpace("-123")).isFalse();

6. Benchmarks

Before we conclude this article, let's go through some benchmark results to help us to analyze which of the above-mentioned methods are best for our use-case.

6.1. Simple Benchmark

First, we take a simple approach. We pick one string value – for our test we use Integer.MAX_VALUE. Then, that value will be tested against all our implementations:

Benchmark Mode Cnt Score Error Units Benchmarking.usingCoreJava avgt 20 57.241 ± 0.792 ns/op Benchmarking.usingNumberUtils_isCreatable avgt 20 26.711 ± 1.110 ns/op Benchmarking.usingNumberUtils_isParsable avgt 20 46.577 ± 1.973 ns/op Benchmarking.usingRegularExpressions avgt 20 101.580 ± 4.244 ns/op Benchmarking.usingStringUtils_isNumeric avgt 20 35.885 ± 1.691 ns/op Benchmarking.usingStringUtils_isNumericSpace avgt 20 31.979 ± 1.393 ns/op

As we see, the most costly operations are regular expressions. After that is our core Java-based solution.

Moreover, note that the operations using the Apache Commons library are by-and-large the same.

6.2. Enhanced Benchmark

Let's use a more diverse set of tests, for a more representative benchmark:

  • 95 values are numeric (0-94 and Integer.MAX_VALUE)
  • 3 contain numbers but are still malformatted — ‘x0‘, ‘0..005′, and ‘–11
  • 1 contains only text
  • 1 is a null

Upon executing the same tests, we'll see the results:

Benchmark Mode Cnt Score Error Units Benchmarking.usingCoreJava avgt 20 10162.872 ± 798.387 ns/op Benchmarking.usingNumberUtils_isCreatable avgt 20 1703.243 ± 108.244 ns/op Benchmarking.usingNumberUtils_isParsable avgt 20 1589.915 ± 203.052 ns/op Benchmarking.usingRegularExpressions avgt 20 7168.761 ± 344.597 ns/op Benchmarking.usingStringUtils_isNumeric avgt 20 1071.753 ± 8.657 ns/op Benchmarking.usingStringUtils_isNumericSpace avgt 20 1157.722 ± 24.139 ns/op

The most important difference is that two of our tests – the regular expressions solution and the core Java-based solution – have traded places.

A partir de este resultado, aprendemos que lanzar y manejar la excepción NumberFormatException , que ocurre en solo el 5% de los casos, tiene un impacto relativamente grande en el rendimiento general. Entonces, concluimos, que la solución óptima depende de nuestra entrada esperada.

Además, podemos concluir con seguridad que debemos usar los métodos de la biblioteca Commons o un método implementado de manera similar para un rendimiento óptimo.

7. Conclusión

En este artículo, exploramos diferentes formas de encontrar si una cadena es numérica o no. Analizamos ambas soluciones: métodos integrados y también bibliotecas externas.

Como siempre, la implementación de todos los ejemplos y fragmentos de código dados anteriormente, incluido el código utilizado para realizar evaluaciones comparativas, se puede encontrar en GitHub.