Verificați dacă un șir este numeric în Java

1. Introducere

Adesea, în timp ce operăm pe șiruri , trebuie să ne dăm seama dacă un șir este sau nu un număr valid.

În acest tutorial, vom explora mai multe moduri de a detecta dacă șirul dat este numeric , folosind mai întâi Java simplu, apoi expresii regulate și în cele din urmă utilizând biblioteci externe.

După ce am terminat discutarea diferitelor implementări, vom folosi parametrii de referință pentru a ne face o idee despre metodele optime.

2. Condiții prealabile

Să începem cu câteva condiții prealabile înainte de a trece la conținutul principal.

În ultima parte a acestui articol, vom folosi biblioteca externă Apache Commons pentru care vom adăuga dependența sa în pom.xml :

 org.apache.commons commons-lang3 3.9 

Cea mai recentă versiune a acestei biblioteci poate fi găsită pe Maven Central.

3. Utilizarea Java simplă

Poate că cel mai simplu și cel mai fiabil mod de a verifica dacă un șir este numeric sau nu este analizându-l folosind metodele încorporate Java:

  1. Integer.parseInt (String)
  2. Float.parseFloat (șir)
  3. Double.parseDouble (String)
  4. Long.parseLong (String)
  5. nou BigInteger (String)

Dacă aceste metode nu aruncă nici o excepție NumberFormatException , atunci înseamnă că analiza a avut succes, iar șirul este numeric:

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

Să vedem această metodă în acțiune:

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

În metoda noastră isNumeric () , verificăm doar valorile de tip Double , dar această metodă poate fi modificată și pentru a verifica numerele întregi , plutitoare , lungi și mari folosind oricare dintre metodele de analiză pe care le-am înscris mai devreme .

Aceste metode sunt, de asemenea, discutate în articolul Java String Conversions.

4. Utilizarea expresiilor regulate

Acum să folosim regex -? \ D + (\. \ D +)? pentru a se potrivi Șiruri numerice formate din întregul pozitiv sau negativ și flotante.

Dar acest lucru este de la sine înțeles, că putem modifica cu siguranță această regex pentru a identifica și a gestiona o gamă largă de reguli. Aici, o vom simplifica.

Să descompunem această regex și să vedem cum funcționează:

  • -? - această parte identifică dacă numărul dat este negativ, liniuța „ - ” caută literalmente liniuța și semnul întrebării „ ? ”Își marchează prezența ca opțională
  • \ d + - caută una sau mai multe cifre
  • (\. \ d +)? - această parte a regexului este de a identifica numerele flotante. Aici căutăm una sau mai multe cifre urmate de un punct. Semnul de întrebare, în cele din urmă, înseamnă că acest grup complet este opțional

Expresiile regulate sunt un subiect foarte larg. Pentru a obține o scurtă prezentare generală, consultați tutorialul nostru despre API-ul expresiilor regulate Java.

Deocamdată, să creăm o metodă folosind expresia regulată de mai sus:

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

Să vedem acum câteva afirmații pentru metoda de mai sus:

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

5. Utilizarea Apache Commons

În această secțiune, vom discuta despre diferite metode disponibile în biblioteca Apache Commons.

5.1. NumberUtils.isCreatable (String)

NumberUtils din Apache Commons oferă o metodă statică NumberUtils.isCreatable (String) care verifică dacă un String este un număr Java valid sau nu.

Această metodă acceptă:

  1. Numere hexazecimale începând cu 0x sau 0X
  2. Numere octale începând cu un prim 0
  3. Notatie stiintifica (de exemplu 1.05e-10)
  4. Numere marcate cu un calificativ de tip (de exemplu 1L sau 2.2d)

Dacă șirul furnizat este nul sau gol / gol , atunci nu este considerat un număr și metoda va reveni falsă .

Să executăm câteva teste folosind această metodă:

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

Rețineți cum obținem afirmații adevărate pentru numere hexazecimale, numere octale și notații științifice în rândurile 6, 7 și respectiv 8.

De asemenea, pe linia 14, șirul „09” returnează fals deoarece „0” precedent indică faptul că acesta este un număr octal și „09” nu este un număr octal valid.

Pentru fiecare intrare , care returnează adevărat cu această metodă, putem folosi NumberUtils.createNumber (String) , care ne va da numărul valid.

5.2. NumberUtils.isParsable (String)

Metoda NumberUtils.isParsable (String) verifică dacă șirul dat este parsabil sau nu.

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.

Din acest rezultat, aflăm că aruncarea și manipularea excepției NumberFormatException , care are loc doar în 5% din cazuri, are un impact relativ mare asupra performanței generale. Deci, concluzionăm, că soluția optimă depinde de intrarea noastră așteptată.

De asemenea, putem concluziona în siguranță că ar trebui să folosim metodele din biblioteca Commons sau o metodă implementată în mod similar pentru performanțe optime.

7. Concluzie

În acest articol, am explorat diferite moduri de a afla dacă un șir este numeric sau nu. Ne-am uitat la ambele soluții - metode încorporate și, de asemenea, biblioteci externe.

Ca întotdeauna, implementarea tuturor exemplelor și a fragmentelor de cod date mai sus, inclusiv a codului folosit pentru a efectua repere, poate fi găsită pe GitHub.