1. Введение
Часто при работе с String
нам нужно выяснить, является ли String
допустимым числом или нет.
В этом руководстве мы рассмотрим несколько способов определения того, является ли заданная строка
числовой , сначала используя обычную Java, затем регулярные выражения и, наконец, используя внешние библиотеки.
Когда мы закончим обсуждение различных реализаций, мы воспользуемся эталонными тестами, чтобы понять, какие методы являются оптимальными.
2. Предпосылки
Давайте начнем с некоторых предварительных условий, прежде чем мы перейдем к основному содержанию.
В последней части этой статьи мы будем использовать внешнюю библиотеку Apache Commons, чтобы добавить ее зависимость в наш pom.xml
:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
Последнюю версию этой библиотеки можно найти на Maven Central .
3. Использование простой Java
Возможно, самый простой и надежный способ проверить, является ли строка
числовой или нет, — это проанализировать ее с помощью встроенных методов Java:
Целое.parseInt(Строка)
Float.parseFloat (строка)
Double.parseDouble (строка)
Long.parseLong(строка)
новый 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 String Conversions .
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 или нет.
Этот метод принимает:
- Шестнадцатеричные числа, начинающиеся с 0x или 0X
- Восьмеричные числа, начинающиеся с ведущего 0
- Научное обозначение (например, 1.05e-10)
- Числа, отмеченные квалификатором типа (например, 1L или 2.2d)
Если предоставленная строка имеет значение null
или empty/blank
, то она не считается числом, и метод вернет 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)
проверяет, поддается ли синтаксическому анализу данная строка
.
Поддающиеся анализу числа — это те, которые успешно проанализированы любым методом синтаксического анализа, таким как Integer.parseInt(String)
, Long.parseLong(String)
, Float.parseFloat(String)
или Double.parseDouble(String)
.
В отличие от NumberUtils.isCreatable()
, этот метод не будет принимать шестнадцатеричные числа, экспоненциальные обозначения или строки, заканчивающиеся квалификатором любого типа, например «f», «F», «d», «D», «l»
или «L». '
.
Давайте посмотрим на некоторые утверждения:
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();
В строке 4, в отличие от NumberUtils.isCreatable()
, число, начинающееся со строки «0»
, считается не восьмеричным числом, а обычным десятичным числом, и поэтому оно возвращает true.
Мы можем использовать этот метод в качестве замены тому, что мы делали в разделе 3, где мы пытаемся проанализировать число и проверить наличие ошибки.
5.3. StringUtils.isNumeric(CharSequence
)
Метод StringUtils.isNumeric(CharSequence)
строго проверяет цифры Юникода. Это означает:
- Допустимы любые цифры из любого языка, который является цифрой Unicode.
- Поскольку десятичная точка не считается цифрой Unicode, она недействительна.
- Ведущие знаки (как положительные, так и отрицательные) также недопустимы.
Теперь давайте посмотрим на этот метод в действии:
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();
Обратите внимание, что входные параметры в строках 2 и 3 представляют числа 123
на арабском и деванагари соответственно. Поскольку они являются допустимыми цифрами Юникода, этот метод возвращает для них значение true
.
5.4. StringUtils.isNumericSpace(CharSequence)
StringUtils.isNumericSpace (CharSequence)
строго проверяет наличие цифр и/или пробелов в Юникоде. Это то же самое, что и StringUtils.isNumeric()
, за исключением того, что он также принимает пробелы, причем не только начальные и конечные пробелы, но и те, которые находятся между числами:
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. Ориентиры
Прежде чем мы завершим эту статью, давайте рассмотрим некоторые результаты тестов, которые помогут нам проанализировать, какие из вышеупомянутых методов лучше всего подходят для нашего случая использования.
6.1. Простой тест
Во-первых, мы используем простой подход. Мы выбираем одно строковое значение — для нашего теста мы используем Integer.MAX_VALUE
. Затем это значение будет проверено на всех наших реализациях:
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
Как видим, самые затратные операции — это регулярные выражения. После этого наше основное решение на основе Java.
Более того, обратите внимание, что операции с использованием библиотеки Apache Commons в целом одинаковы.
6.2. Расширенный контрольный показатель
Давайте используем более разнообразный набор тестов для более репрезентативного бенчмарка:
- 95 значений являются числовыми (0-94 и
Integer.MAX_VALUE
) - 3 содержат числа, но все еще имеют неправильный формат — «
x0
», «0.005
» и «-11
». - 1 содержит только текст
- 1 является
нулем
Выполнив те же тесты, мы увидим результаты:
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
Самое важное отличие состоит в том, что два наших теста, решение с регулярными выражениями и базовое решение на основе Java, поменялись местами.
Из этого результата мы узнаем, что генерация и обработка NumberFormatException
, которые происходят только в 5% случаев, имеют относительно большое влияние на общую производительность. Таким образом, мы можем сделать вывод, что оптимальное решение зависит от наших ожидаемых входных данных.
Также мы можем с уверенностью заключить, что для оптимальной производительности следует использовать методы из библиотеки Commons или метод, реализованный аналогичным образом.
7. Заключение
В этой статье мы рассмотрели различные способы определить, является ли строка
числовой или нет. Мы рассмотрели оба решения — встроенные методы и внешние библиотеки.
Как всегда, реализацию всех приведенных выше примеров и фрагментов кода, включая код, используемый для выполнения тестов, можно найти на GitHub .