Kontrollera om en sträng är numerisk i Java

1. Introduktion

Ofta när vi arbetar på strängar , måste vi ta reda på om en sträng är ett giltigt nummer eller inte.

I den här handledningen utforskar vi flera sätt att upptäcka om den givna strängen är numerisk , först med vanlig Java, sedan med reguljära uttryck och slutligen med hjälp av externa bibliotek.

När vi är färdiga med att diskutera olika implementeringar använder vi riktmärken för att få en uppfattning om vilka metoder som är optimala.

2. Förutsättningar

Låt oss börja med några förutsättningar innan vi går vidare till huvudinnehållet.

I den senare delen av den här artikeln använder vi det externa biblioteket Apache Commons som vi lägger till dess beroende i vår pom.xml :

 org.apache.commons commons-lang3 3.9 

Den senaste versionen av detta bibliotek finns på Maven Central.

3. Använda vanlig Java

Det enklaste och mest pålitliga sättet att kontrollera om en sträng är numerisk eller inte är kanske genom att analysera den med hjälp av Java: s inbyggda metoder:

  1. Integer.parseInt (String)
  2. Float.parseFloat (String)
  3. Double.parseDouble (String)
  4. Long.parseLong (String)
  5. nya BigInteger (sträng)

Om dessa metoder inte kastar något NumberFormatException betyder det att tolkningen lyckades och strängen är numerisk:

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

Låt oss se den här metoden i aktion:

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

I vår isNumeric () -metod kontrollerar vi bara om värden är av typen Double , men den här metoden kan också modifieras för att kontrollera om Integer , Float , Long och stort antal med någon av de analysmetoder som vi har anlitat tidigare .

Dessa metoder diskuteras också i artikeln om Java-strängkonverteringar.

4. Använda reguljära uttryck

Låt oss nu använda regex -? \ D + (\. \ D +)? för att matcha numeriska strängar som består av det positiva eller negativa heltalet och flottörerna.

Men det är självklart att vi definitivt kan ändra denna regex för att identifiera och hantera ett brett spektrum av regler. Här håller vi det enkelt.

Låt oss bryta ner denna regex och se hur det fungerar:

  • -? - den här delen identifierar om det angivna numret är negativt, strecket " - " söker efter streck bokstavligen och frågetecknet " ? ”Markerar sin närvaro som en valfri
  • \ d + - det här söker efter en eller flera siffror
  • (\. \ d +)? - den här delen av regex är att identifiera flytnummer. Här söker vi efter en eller flera siffror följt av en punkt. Frågetecknet betyder i slutändan att denna hela grupp är valfri

Regulära uttryck är ett mycket brett ämne. För att få en kort översikt, kolla vår handledning om Java Regular Expressions API.

Låt oss för närvarande skapa en metod med ovanstående reguljära uttryck:

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

Låt oss nu titta på några påståenden för ovanstående metod:

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

5. Använda Apache Commons

I det här avsnittet diskuterar vi olika metoder som finns tillgängliga i Apache Commons-biblioteket.

5.1. NumberUtils.isCreatable (String)

NumberUtils från Apache Commons ger en statisk metod NumberUtils.isCreatable (String) som kontrollerar om en String är ett giltigt Java-nummer eller inte.

Denna metod accepterar:

  1. Hexadecimala tal som börjar med 0x eller 0X
  2. Oktala nummer som börjar med ett ledande 0
  3. Vetenskaplig notation (till exempel 1.05e-10)
  4. Nummer markerade med en typkvalificering (till exempel 1L eller 2.2d)

Om den medföljande strängen är null eller tom / tom , anses den inte som ett nummer och metoden returnerar falskt .

Låt oss köra några tester med den här metoden:

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

Observera hur vi får sanna påståenden för hexadecimala tal, oktala siffror och vetenskapliga notationer i rad 6, 7 respektive 8.

På rad 14 returnerar också strängen "09" falsk eftersom föregående "0" indikerar att detta är ett oktalt tal och "09" inte är ett giltigt oktalt tal.

För varje ingång som returnerar sant med den här metoden kan vi använda NumberUtils.createNumber (String) som ger oss ett giltigt nummer.

5.2. NumberUtils.isParsable (String)

De NumberUtils.isParsable (String) metoden kontrollerar huruvida den givna String är parsable eller inte.

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.

Av detta resultat lär vi oss att kastning och hantering av NumberFormatException , som förekommer i endast 5% av fallen, har en relativt stor inverkan på den totala prestandan. Så vi drar slutsatsen att den optimala lösningen beror på vår förväntade input.

Vi kan också säkert dra slutsatsen att vi bör använda metoderna från Commons-biblioteket eller en metod som implementeras på samma sätt för optimal prestanda.

7. Slutsats

I den här artikeln undersökte vi olika sätt att hitta om en sträng är numerisk eller inte. Vi tittade på båda lösningarna - inbyggda metoder och även externa bibliotek.

Som alltid kan implementeringen av alla exempel och kodavsnitt ovan, inklusive koden som används för att utföra riktmärken, hittas på GitHub.