Перевірте, чи рядок є числовим на Java

1. Вступ

Часто оперуючи рядками , нам потрібно з’ясувати, чи є рядок дійсним чисел чи ні.

У цьому підручнику ми розглянемо кілька способів виявити, чи є даний рядок числовим , спочатку за допомогою простої Java, потім регулярних виразів і, нарешті, за допомогою зовнішніх бібліотек.

Закінчивши обговорення різних реалізацій, ми використаємо тести, щоб отримати уявлення про те, які методи є оптимальними.

2. Передумови

Почнемо з деяких передумов, перш ніж переходити до основного змісту.

В останній частині цієї статті ми будемо використовувати зовнішню бібліотеку Apache Commons, для якої ми додамо її залежність у наш pom.xml :

 org.apache.commons commons-lang3 3.9 

Останню версію цієї бібліотеки можна знайти на Maven Central.

3. Використання звичайної Java

Мабуть, найпростіший і найнадійніший спосіб перевірити, чи є рядок числовим чи ні, - це його синтаксичний аналіз за допомогою вбудованих методів Java:

  1. Integer.parseInt (рядок)
  2. Float.parseFloat (рядок)
  3. Double.parseDouble (рядок)
  4. Long.parseLong (рядок)
  5. новий BigInteger (рядок)

Якщо ці методи не викидають жодного NumberFormatException , то це означає, що синтаксичний розбір був успішним, а рядок числовим:

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

Давайте розглянемо цей метод у дії:

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

У нашому методі isNumeric () ми просто перевіряємо значення типу Double , але цей метод також може бути змінений для перевірки на Integer , Float , Long і large numbers, використовуючи будь-який із методів синтаксичного аналізу, які ми перерахували раніше .

Ці методи також обговорюються в статті Перетворення рядків Java.

4. Використання регулярних виразів

Тепер використаємо регулярний вираз -? \ D + (\. \ D +)? для збігу числових рядків, що складаються з додатного чи від’ємного цілого числа та плаваючих знаків.

Але це само собою зрозуміло, що ми можемо точно змінити цей регулярний вираз, щоб визначити та обробляти широкий спектр правил. Тут ми будемо робити це просто.

Давайте розберемо цей регулярний вираз і подивимось, як він працює:

  • -? - ця частина визначає, якщо вказане число від’ємне, тире « - » шукає тире буквально, а знак запитання « ? ”Позначає свою присутність як необов’язкову
  • \ d + - здійснюється пошук однієї або кількох цифр
  • (\. \ d +)? - ця частина регулярного виразу полягає в ідентифікації плаваючих чисел. Тут ми шукаємо одну або кілька цифр, після яких крапка. Знак питання, зрештою, означає, що ця повна група є необов’язковою

Регулярні вислови - дуже широка тема. Щоб отримати короткий огляд, перегляньте наш посібник з API регулярних виразів Java.

На даний момент давайте створимо метод, використовуючи наведений вище регулярний вираз:

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

Давайте тепер розглянемо деякі твердження щодо вищезазначеного методу:

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

5. Використання Apache Commons

У цьому розділі ми обговоримо різні методи, доступні в бібліотеці Apache Commons.

5.1. NumberUtils.isCreatable (рядок)

NumberUtils від Apache Commons надає статичний метод NumberUtils.isCreatable (String), який перевіряє, чи є String дійсним номером Java.

Цей метод приймає:

  1. Шістнадцяткові числа, що починаються з 0x або 0X
  2. Вісімкові числа, що починаються з провідного 0
  3. Наукові позначення (наприклад, 1.05e-10)
  4. Номери, позначені класифікатором типу (наприклад, 1L або 2.2d)

Якщо вказаний рядок є нульовим або порожнім / порожнім , тоді він не вважається числом, і метод поверне false .

Давайте проведемо кілька тестів, використовуючи цей метод:

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

Зверніть увагу, як ми отримуємо справжні твердження щодо шістнадцяткових чисел, вісімкових чисел та наукових позначень у рядках 6, 7 та 8 відповідно.

Крім того, у рядку 14 рядок "09" повертає false, оскільки попереднє "0" вказує, що це вісімкове число, а "09" не є дійсним восьмеричним числом.

Для кожного введення, яке повертає true за допомогою цього методу, ми можемо використовувати NumberUtils.createNumber (String), який дасть нам дійсне число.

5.2. NumberUtils.isParsable (рядок)

Метод NumberUtils.isParsable (String) перевіряє, чи є даний рядок синтаксичним чи ні.

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.

З цього результату ми дізнаємось, що метання та обробка NumberFormatException , яке трапляється лише у 5% випадків, має відносно великий вплив на загальну продуктивність. Отже, ми робимо висновок, що оптимальне рішення залежить від нашого очікуваного вкладу.

Крім того, ми можемо сміливо дійти висновку, що нам слід використовувати методи з бібліотеки Commons або метод, реалізований аналогічним чином для оптимальної роботи.

7. Висновок

У цій статті ми дослідили різні способи з’ясувати, чи є рядок числовим чи ні. Ми розглянули обидва рішення - вбудовані методи, а також зовнішні бібліотеки.

Як завжди, реалізацію всіх наведених вище прикладів та фрагментів коду, включаючи код, що використовується для виконання тестів, можна знайти на GitHub.