How to check if a String is numeric in Java

2018-12-31 01:02发布

How would you check if a String was a number before parsing it?

30条回答
爱死公子算了
2楼-- · 2018-12-31 01:40

As @CraigTP had mentioned in his excellent answer, I also have similar performance concerns on using Exceptions to test whether the string is numerical or not. So I end up splitting the string and use java.lang.Character.isDigit().

public static boolean isNumeric(String str)
{
    for (char c : str.toCharArray())
    {
        if (!Character.isDigit(c)) return false;
    }
    return true;
}

According to the Javadoc, Character.isDigit(char) will correctly recognizes non-Latin digits. Performance-wise, I think a simple N number of comparisons where N is the number of characters in the string would be more computationally efficient than doing a regex matching.

UPDATE: As pointed by Jean-François Corbett in the comment, the above code would only validate positive integers, which covers the majority of my use case. Below is the updated code that correctly validates decimal numbers according to the default locale used in your system, with the assumption that decimal separator only occur once in the string.

public static boolean isStringNumeric( String str )
{
    DecimalFormatSymbols currentLocaleSymbols = DecimalFormatSymbols.getInstance();
    char localeMinusSign = currentLocaleSymbols.getMinusSign();

    if ( !Character.isDigit( str.charAt( 0 ) ) && str.charAt( 0 ) != localeMinusSign ) return false;

    boolean isDecimalSeparatorFound = false;
    char localeDecimalSeparator = currentLocaleSymbols.getDecimalSeparator();

    for ( char c : str.substring( 1 ).toCharArray() )
    {
        if ( !Character.isDigit( c ) )
        {
            if ( c == localeDecimalSeparator && !isDecimalSeparatorFound )
            {
                isDecimalSeparatorFound = true;
                continue;
            }
            return false;
        }
    }
    return true;
}
查看更多
ら面具成の殇う
3楼-- · 2018-12-31 01:40

Why is everyone pushing for exception/regex solutions?

While I can understand most people are fine with using try/catch, if you want to do it frequently... it can be extremely taxing.

What I did here was take the regex, the parseNumber() methods, and the array searching method to see which was the most efficient. This time, I only looked at integer numbers.

public static boolean isNumericRegex(String str) {
    if (str == null)
        return false;
    return str.matches("-?\\d+");
}

public static boolean isNumericArray(String str) {
    if (str == null)
        return false;
    char[] data = str.toCharArray();
    if (data.length <= 0)
        return false;
    int index = 0;
    if (data[0] == '-' && data.length > 1)
        index = 1;
    for (; index < data.length; index++) {
        if (data[index] < '0' || data[index] > '9') // Character.isDigit() can go here too.
            return false;
    }
    return true;
}

public static boolean isNumericException(String str) {
    if (str == null)
        return false;
    try {  
        /* int i = */ Integer.parseInt(str);
    } catch (NumberFormatException nfe) {  
        return false;  
    }
    return true;
}

The results in speed I got were:

Done with: for (int i = 0; i < 10000000; i++)...

With only valid numbers ("59815833" and "-59815833"):
    Array numeric took 395.808192 ms [39.5808192 ns each]
    Regex took 2609.262595 ms [260.9262595 ns each]
    Exception numeric took 428.050207 ms [42.8050207 ns each]
    // Negative sign
    Array numeric took 355.788273 ms [35.5788273 ns each]
    Regex took 2746.278466 ms [274.6278466 ns each]
    Exception numeric took 518.989902 ms [51.8989902 ns each]
    // Single value ("1")
    Array numeric took 317.861267 ms [31.7861267 ns each]
    Regex took 2505.313201 ms [250.5313201 ns each]
    Exception numeric took 239.956955 ms [23.9956955 ns each]
    // With Character.isDigit()
    Array numeric took 400.734616 ms [40.0734616 ns each]
    Regex took 2663.052417 ms [266.3052417 ns each]
    Exception numeric took 401.235906 ms [40.1235906 ns each]

With invalid characters ("5981a5833" and "a"):
    Array numeric took 343.205793 ms [34.3205793 ns each]
    Regex took 2608.739933 ms [260.8739933 ns each]
    Exception numeric took 7317.201775 ms [731.7201775 ns each]
    // With a single character ("a")
    Array numeric took 291.695519 ms [29.1695519 ns each]
    Regex took 2287.25378 ms [228.725378 ns each]
    Exception numeric took 7095.969481 ms [709.5969481 ns each]

With null:
    Array numeric took 214.663834 ms [21.4663834 ns each]
    Regex took 201.395992 ms [20.1395992 ns each]
    Exception numeric took 233.049327 ms [23.3049327 ns each]
    Exception numeric took 6603.669427 ms [660.3669427 ns each] if there is no if/null check

Disclaimer: I'm not claiming these methods are 100% optimized, they're just for demonstration of the data

Exceptions won if and only if the number is 4 characters or less, and every string is always a number... in which case, why even have a check?

In short, it is extremely painful if you run into invalid numbers frequently with the try/catch, which makes sense. An important rule I always follow is NEVER use try/catch for program flow. This is an example why.

Interestingly, the simple if char <0 || >9 was extremely simple to write, easy to remember (and should work in multiple languages) and wins almost all the test scenarios.

The only downside is that I'm guessing Integer.parseInt() might handle non ASCII numbers, whereas the array searching method does not.


For those wondering why I said it's easy to remember the character array one, if you know there's no negative signs, you can easily get away with something condensed as this:

public static boolean isNumericArray(String str) {
    if (str == null)
        return false;
    for (char c : str.toCharArray())
        if (c < '0' || c > '9')
            return false;
    return true;

Lastly as a final note, I was curious about the assigment operator in the accepted example with all the votes up. Adding in the assignment of

double d = Double.parseDouble(...)

is not only useless since you don't even use the value, but it wastes processing time and increased the runtime by a few nanoseconds (which led to a 100-200 ms increase in the tests). I can't see why anyone would do that since it actually is extra work to reduce performance.

You'd think that would be optimized out... though maybe I should check the bytecode and see what the compiler is doing. That doesn't explain why it always showed up as lengthier for me though if it somehow is optimized out... therefore I wonder what's going on. As a note: By lengthier, I mean running the test for 10000000 iterations, and running that program multiple times (10x+) always showed it to be slower.

EDIT: Updated a test for Character.isDigit()

查看更多
流年柔荑漫光年
4楼-- · 2018-12-31 01:42

To match only positive base-ten integers, that contains only ASCII digits, use:

public static boolean isNumeric(String maybeNumeric) {
    return maybeNumeric != null && maybeNumeric.matches("[0-9]+");
}
查看更多
爱死公子算了
5楼-- · 2018-12-31 01:42

With Apache Commons Lang 3.5 and above: NumberUtils.isCreatable or StringUtils.isNumeric.

With Apache Commons Lang 3.4 and below: NumberUtils.isNumber or StringUtils.isNumeric.

You can also use StringUtils.isNumericSpace which returns true for empty strings and ignores internal spaces in the string. (The linked javadocs contain detailed examples for each method.)

查看更多
像晚风撩人
6楼-- · 2018-12-31 01:43

If you using java to develop Android app, you could using TextUtils.isDigitsOnly function.

查看更多
若你有天会懂
7楼-- · 2018-12-31 01:43

That's why I like the Try* approach in .NET. In addition to the traditional Parse method that's like the Java one, you also have a TryParse method. I'm not good in Java syntax (out parameters?), so please treat the following as some kind of pseudo-code. It should make the concept clear though.

boolean parseInteger(String s, out int number)
{
    try {
        number = Integer.parseInt(myString);
        return true;
    } catch(NumberFormatException e) {
        return false;
    }
}

Usage:

int num;
if (parseInteger("23", out num)) {
    // Do something with num.
}
查看更多
登录 后发表回答