Проверьте, является ли строка числовой в Java

1. Введение

Часто, работая с String s, нам нужно выяснить, является ли String допустимым числом или нет.

В этом руководстве мы рассмотрим несколько способов определить, является ли данная строка числовой , сначала с использованием простой 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 , это означает, что синтаксический анализ был успешным и String является числовым:

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 , но этот метод также можно изменить для проверки целых , плавающих , длинных и больших чисел с помощью любого из методов синтаксического анализа, которые мы зачислили ранее. .

Эти методы также обсуждаются в статье о преобразованиях строк в 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), который проверяет, является ли строка допустимым номером Java или нет.

Этот метод принимает:

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

Если подаваемая строка нулевая или пустые / пустое , то это не считается количеством и метод возвращает ложь .

Давайте запустим несколько тестов, используя этот метод:

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.