Vérifier si une chaîne est numérique en Java

1. Introduction

Souvent, en opérant sur des chaînes , nous devons déterminer si une chaîne est un nombre valide ou non.

Dans ce didacticiel, nous explorerons plusieurs façons de détecter si la chaîne donnée est numérique , en utilisant d'abord Java brut, puis des expressions régulières et enfin en utilisant des bibliothèques externes.

Une fois que nous aurons fini de discuter des différentes implémentations, nous utiliserons des benchmarks pour avoir une idée des méthodes optimales.

2. Prérequis

Commençons par quelques prérequis avant de passer au contenu principal.

Dans la dernière partie de cet article, nous utiliserons la bibliothèque externe Apache Commons pour laquelle nous ajouterons sa dépendance dans notre pom.xml :

 org.apache.commons commons-lang3 3.9 

La dernière version de cette bibliothèque est disponible sur Maven Central.

3. Utilisation de Java brut

Le moyen le plus simple et le plus fiable de vérifier si une chaîne est numérique ou non est de l'analyser à l'aide des méthodes intégrées de Java:

  1. Integer.parseInt (chaîne)
  2. Float.parseFloat (chaîne)
  3. Double.parseDouble (chaîne)
  4. Long.parseLong (Chaîne)
  5. new BigInteger (String)

Si ces méthodes ne lèvent aucune NumberFormatException , cela signifie que l'analyse a réussi et que la chaîne est numérique:

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

Voyons cette méthode en action:

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();

Dans notre méthode isNumeric () , nous vérifions simplement les valeurs de type Double , mais cette méthode peut également être modifiée pour vérifier les nombres entiers , flottants , longs et grands en utilisant l'une des méthodes d'analyse que nous avons enrôlées précédemment .

Ces méthodes sont également décrites dans l'article Conversions de chaînes Java.

4. Utilisation d'expressions régulières

Utilisons maintenant regex -? \ D + (\. \ D +)? pour faire correspondre les chaînes numériques constituées d'un entier positif ou négatif et de flottants.

Mais cela va sans dire, que nous pouvons certainement modifier cette expression régulière pour identifier et gérer un large éventail de règles. Ici, nous allons garder les choses simples.

Décomposons cette expression régulière et voyons comment cela fonctionne:

  • -? - cette partie identifie si le nombre donné est négatif, le tiret « - » recherche littéralement le tiret et le point d'interrogation « ? "Marque sa présence comme facultative
  • \ d + - ceci recherche un ou plusieurs chiffres
  • (\. \ d +)? - cette partie de l'expression régulière consiste à identifier les nombres flottants. Ici, nous recherchons un ou plusieurs chiffres suivis d'un point. Le point d'interrogation, au final, signifie que ce groupe complet est facultatif

Les expressions régulières sont un sujet très vaste. Pour obtenir un bref aperçu, consultez notre tutoriel sur l'API des expressions régulières Java.

Pour l'instant, créons une méthode utilisant l'expression régulière ci-dessus:

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

Regardons maintenant quelques assertions pour la méthode ci-dessus:

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

5. Utilisation d'Apache Commons

Dans cette section, nous discuterons des différentes méthodes disponibles dans la bibliothèque Apache Commons.

5.1. NumberUtils.isCreatable (chaîne)

NumberUtils d'Apache Commons fournit une méthode statique NumberUtils.isCreatable (String) qui vérifie si une chaîne est un nombre Java valide ou non.

Cette méthode accepte:

  1. Nombres hexadécimaux commençant par 0x ou 0X
  2. Nombres octaux commençant par un 0 non significatif
  3. Notation scientifique (par exemple 1.05e-10)
  4. Numéros marqués d'un qualificatif de type (par exemple 1L ou 2.2d)

Si la chaîne fournie est nulle ou vide / vide , elle n'est pas considérée comme un nombre et la méthode renverra false .

Exécutons quelques tests en utilisant cette méthode:

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();

Notez comment nous obtenons de vraies assertions pour les nombres hexadécimaux, les nombres octaux et les notations scientifiques aux lignes 6, 7 et 8 respectivement.

De plus, à la ligne 14, la chaîne «09» renvoie faux car le «0» précédent indique qu'il s'agit d'un nombre octal et que «09» n'est pas un nombre octal valide.

Pour chaque entrée qui retourne true avec cette méthode, nous pouvons utiliser NumberUtils.createNumber (String) qui nous donnera le nombre valide.

5.2. NumberUtils.isParsable (chaîne)

La méthode NumberUtils.isParsable (String) vérifie si la chaîne donnée est analysable ou non.

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.

De ce résultat, nous apprenons que le lancement et la gestion de l' exception NumberFormatException , qui se produit dans seulement 5% des cas, a un impact relativement important sur les performances globales. Nous concluons donc que la solution optimale dépend de notre contribution attendue.

En outre, nous pouvons conclure en toute sécurité que nous devrions utiliser les méthodes de la bibliothèque Commons ou une méthode implémentée de manière similaire pour des performances optimales.

7. Conclusion

Dans cet article, nous avons exploré différentes manières de déterminer si une chaîne est numérique ou non. Nous avons examiné les deux solutions - méthodes intégrées et bibliothèques externes.

Comme toujours, l'implémentation de tous les exemples et extraits de code donnés ci-dessus, y compris le code utilisé pour effectuer des tests de performances, peut être trouvée sur GitHub.