What's the best way to check if a String repre

2018-12-31 14:11发布

问题:

I normally use the following idiom to check if a String can be converted to an integer.

public boolean isInteger( String input ) {
    try {
        Integer.parseInt( input );
        return true;
    }
    catch( Exception e ) {
        return false;
    }
}

Is it just me, or does this seem a bit hackish? What\'s a better way?


See my answer (with benchmarks, based on the earlier answer by CodingWithSpike) to see why I\'ve reversed my position and accepted Jonas Klemming\'s answer to this problem. I think this original code will be used by most people because it\'s quicker to implement, and more maintainable, but it\'s orders of magnitude slower when non-integer data is provided.

回答1:

If you are not concerned with potential overflow problems this function will perform about 20-30 times faster than using Integer.parseInt().

public static boolean isInteger(String str) {
    if (str == null) {
        return false;
    }
    if (str.isEmpty()) {
        return false;
    }
    int i = 0;
    if (str.charAt(0) == \'-\') {
        if (length == 1) {
            return false;
        }
        i = 1;
    }
    for (; i < length; i++) {
        char c = str.charAt(i);
        if (c < \'0\' || c > \'9\') {
            return false;
        }
    }
    return true;
}


回答2:

You have it, but you should only catch NumberFormatException.



回答3:

Did a quick benchmark. Exceptions aren\'t actually that expensivve, unless you start popping back multiple methods and the JVM has to do a lot of work to get the execution stack in place. When staying in the same method, they aren\'t bad performers.

 public void RunTests()
 {
     String str = \"1234567890\";

     long startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByException(str);
     long endTime = System.currentTimeMillis();
     System.out.print(\"ByException: \");
     System.out.println(endTime - startTime);

     startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByRegex(str);
     endTime = System.currentTimeMillis();
     System.out.print(\"ByRegex: \");
     System.out.println(endTime - startTime);

     startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByJonas(str);
     endTime = System.currentTimeMillis();
     System.out.print(\"ByJonas: \");
     System.out.println(endTime - startTime);
 }

 private boolean IsInt_ByException(String str)
 {
     try
     {
         Integer.parseInt(str);
         return true;
     }
     catch(NumberFormatException nfe)
     {
         return false;
     }
 }

 private boolean IsInt_ByRegex(String str)
 {
     return str.matches(\"^-?\\\\d+$\");
 }

 public boolean IsInt_ByJonas(String str)
 {
     if (str == null) {
             return false;
     }
     int length = str.length();
     if (length == 0) {
             return false;
     }
     int i = 0;
     if (str.charAt(0) == \'-\') {
             if (length == 1) {
                     return false;
             }
             i = 1;
     }
     for (; i < length; i++) {
             char c = str.charAt(i);
             if (c <= \'/\' || c >= \':\') {
                     return false;
             }
     }
     return true;
 }

Output:

ByException: 31

ByRegex: 453 (note: re-compiling the pattern every time)

ByJonas: 16

I do agree that Jonas K\'s solution is the most robust too. Looks like he wins :)



回答4:

Since there\'s possibility that people still visit here and will be biased against Regex after the benchmarks... So i\'m gonna give an updated version of the benchmark, with a compiled version of the Regex. Which opposed to the previous benchmarks, this one shows Regex solution actually has consistently good performance.

Copied from Bill the Lizard and updated with compiled version:

private final Pattern pattern = Pattern.compile(\"^-?\\\\d+$\");

public void runTests() {
    String big_int = \"1234567890\";
    String non_int = \"1234XY7890\";

    long startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByException(big_int);
    long endTime = System.currentTimeMillis();
    System.out.print(\"ByException - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByException(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByException - non-integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"\\nByRegex - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByRegex - non-integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
            IsInt_ByCompiledRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"\\nByCompiledRegex - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
            IsInt_ByCompiledRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByCompiledRegex - non-integer data: \");
    System.out.println(endTime - startTime);


    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByJonas(big_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"\\nByJonas - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByJonas(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByJonas - non-integer data: \");
    System.out.println(endTime - startTime);
}

private boolean IsInt_ByException(String str)
{
    try
    {
        Integer.parseInt(str);
        return true;
    }
    catch(NumberFormatException nfe)
    {
        return false;
    }
}

private boolean IsInt_ByRegex(String str)
{
    return str.matches(\"^-?\\\\d+$\");
}

private boolean IsInt_ByCompiledRegex(String str) {
    return pattern.matcher(str).find();
}

public boolean IsInt_ByJonas(String str)
{
    if (str == null) {
            return false;
    }
    int length = str.length();
    if (length == 0) {
            return false;
    }
    int i = 0;
    if (str.charAt(0) == \'-\') {
            if (length == 1) {
                    return false;
            }
            i = 1;
    }
    for (; i < length; i++) {
            char c = str.charAt(i);
            if (c <= \'/\' || c >= \':\') {
                    return false;
            }
    }
    return true;
}

Results:

ByException - integer data: 45
ByException - non-integer data: 465

ByRegex - integer data: 272
ByRegex - non-integer data: 131

ByCompiledRegex - integer data: 45
ByCompiledRegex - non-integer data: 26

ByJonas - integer data: 8
ByJonas - non-integer data: 2


回答5:

org.apache.commons.lang.StringUtils.isNumeric 

though Java\'s standard lib really misses such utility functions

I think that Apache Commons is a \"must have\" for every Java programmer

too bad it isn\'t ported to Java5 yet



回答6:

It partly depend on what you mean by \"can be converted to an integer\".

If you mean \"can be converted into an int in Java\" then the answer from Jonas is a good start, but doesn\'t quite finish the job. It would pass 999999999999999999999999999999 for example. I would add the normal try/catch call from your own question at the end of the method.

The character-by-character checks will efficiently reject \"not an integer at all\" cases, leaving \"it\'s an integer but Java can\'t handle it\" cases to be caught by the slower exception route. You could do this bit by hand too, but it would be a lot more complicated.



回答7:

Just one comment about regexp. Every example provided here is wrong!. If you want to use regexp don\'t forget that compiling the pattern take a lot of time. This:

str.matches(\"^-?\\\\d+$\")

and also this:

Pattern.matches(\"-?\\\\d+\", input);

causes compile of pattern in every method call. To used it correctly follow:

import java.util.regex.Pattern;

/**
 * @author Rastislav Komara
 */
public class NaturalNumberChecker {
    public static final Pattern PATTERN = Pattern.compile(\"^\\\\d+$\");

    boolean isNaturalNumber(CharSequence input) {
        return input != null && PATTERN.matcher(input).matches();
    }
}


回答8:

I copied the code from rally25rs answer and added some tests for non-integer data. The results are undeniably in favor of the method posted by Jonas Klemming. The results for the Exception method that I originally posted are pretty good when you have integer data, but they\'re the worst when you don\'t, while the results for the RegEx solution (that I\'ll bet a lot of people use) were consistently bad. See Felipe\'s answer for a compiled regex example, which is much faster.

public void runTests()
{
    String big_int = \"1234567890\";
    String non_int = \"1234XY7890\";

    long startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByException(big_int);
    long endTime = System.currentTimeMillis();
    System.out.print(\"ByException - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByException(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByException - non-integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"\\nByRegex - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByRegex - non-integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByJonas(big_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"\\nByJonas - integer data: \");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByJonas(non_int);
    endTime = System.currentTimeMillis();
    System.out.print(\"ByJonas - non-integer data: \");
    System.out.println(endTime - startTime);
}

private boolean IsInt_ByException(String str)
{
    try
    {
        Integer.parseInt(str);
        return true;
    }
    catch(NumberFormatException nfe)
    {
        return false;
    }
}

private boolean IsInt_ByRegex(String str)
{
    return str.matches(\"^-?\\\\d+$\");
}

public boolean IsInt_ByJonas(String str)
{
    if (str == null) {
            return false;
    }
    int length = str.length();
    if (length == 0) {
            return false;
    }
    int i = 0;
    if (str.charAt(0) == \'-\') {
            if (length == 1) {
                    return false;
            }
            i = 1;
    }
    for (; i < length; i++) {
            char c = str.charAt(i);
            if (c <= \'/\' || c >= \':\') {
                    return false;
            }
    }
    return true;
}

Results:

ByException - integer data: 47
ByException - non-integer data: 547

ByRegex - integer data: 390
ByRegex - non-integer data: 313

ByJonas - integer data: 0
ByJonas - non-integer data: 16


回答9:

There is guava version:

import com.google.common.primitives.Ints;

Integer intValue = Ints.tryParse(stringValue);

It will return null instead of throwing an exception if it fails to parse string.



回答10:

This is shorter, but shorter isn\'t necessarily better (and it won\'t catch integer values which are out of range, as pointed out in danatel\'s comment):

input.matches(\"^-?\\\\d+$\");

Personally, since the implementation is squirrelled away in a helper method and correctness trumps length, I would just go with something like what you have (minus catching the base Exception class rather than NumberFormatException).



回答11:

You can use the matches method of the string class. The [0-9] represents all the values it can be, the + means it must be at least one character long, and the * means it can be zero or more characters long.

boolean isNumeric = yourString.matches(\"[0-9]+\"); // 1 or more characters long, numbers only
boolean isNumeric = yourString.matches(\"[0-9]*\"); // 0 or more characters long, numbers only


回答12:

If your String array contains pure Integers and Strings, code below should work. You only have to look at first character. e.g. [\"4\",\"44\",\"abc\",\"77\",\"bond\"]

if (Character.isDigit(string.charAt(0))) {
    //Do something with int
}


回答13:

You can also use the Scanner class, and use hasNextInt() - and this allows you to test for other types, too, like floats, etc.



回答14:

You just check NumberFormatException:-

 String value=\"123\";
 try  
 {  
    int s=Integer.parseInt(any_int_val);
    // do something when integer values comes 
 }  
 catch(NumberFormatException nfe)  
 {  
          // do something when string values comes 
 }  


回答15:

You may try apache utils

NumberUtils.isNumber( myText)

See the javadoc here



回答16:

This is a Java 8 variation of Jonas Klemming answer:

public static boolean isInteger(String str) {
    return str != null && str.length() > 0 &&
         IntStream.range(0, str.length()).allMatch(i -> i == 0 && (str.charAt(i) == \'-\' || str.charAt(i) == \'+\')
                  || Character.isDigit(str.charAt(i)));
}

Test code:

public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
    Arrays.asList(\"1231231\", \"-1232312312\", \"+12313123131\", \"qwqe123123211\", \"2\", \"0000000001111\", \"\", \"123-\", \"++123\",
            \"123-23\", null, \"+-123\").forEach(s -> {
        System.out.printf(\"%15s %s%n\", s, isInteger(s));
    });
}

Results of the test code:

        1231231 true
    -1232312312 true
   +12313123131 true
  qwqe123123211 false
              2 true
  0000000001111 true
                false
           123- false
          ++123 false
         123-23 false
           null false
          +-123 false


回答17:

How about:

return Pattern.matches(\"-?\\\\d+\", input);


回答18:

You probably need to take the use case in account too:

If most of the time you expect numbers to be valid, then catching the exception is only causing a performance overhead when attempting to convert invalid numbers. Whereas calling some isInteger() method and then convert using Integer.parseInt() will always cause a performance overhead for valid numbers - the strings are parsed twice, once by the check and once by the conversion.



回答19:

This is a modification of Jonas\' code that checks if the string is within range to be cast into an integer.

public static boolean isInteger(String str) {
    if (str == null) {
        return false;
    }
    int length = str.length();
    int i = 0;

    // set the length and value for highest positive int or lowest negative int
    int maxlength = 10;
    String maxnum = String.valueOf(Integer.MAX_VALUE);
    if (str.charAt(0) == \'-\') { 
        maxlength = 11;
        i = 1;
        maxnum = String.valueOf(Integer.MIN_VALUE);
    }  

    // verify digit length does not exceed int range
    if (length > maxlength) { 
        return false; 
    }

    // verify that all characters are numbers
    if (maxlength == 11 && length == 1) {
        return false;
    }
    for (int num = i; num < length; num++) {
        char c = str.charAt(num);
        if (c < \'0\' || c > \'9\') {
            return false;
        }
    }

    // verify that number value is within int range
    if (length == maxlength) {
        for (; i < length; i++) {
            if (str.charAt(i) < maxnum.charAt(i)) {
                return true;
            }
            else if (str.charAt(i) > maxnum.charAt(i)) {
                return false;
            }
        }
    }
    return true;
}


回答20:

If you are using the Android API you can use:

TextUtils.isDigitsOnly(str);


回答21:

Another option:

private boolean isNumber(String s) {
    boolean isNumber = true;
    for (char c : s.toCharArray()) {
        isNumber = isNumber && Character.isDigit(c);
    }
    return isNumber;
}


回答22:

If you want to check if the string represents an integer that fits in an int type, I did a little modification to the jonas\' answer, so that strings that represent integers bigger than Integer.MAX_VALUE or smaller than Integer.MIN_VALUE, will now return false. For example: \"3147483647\" will return false because 3147483647 is bigger than 2147483647, and likewise, \"-2147483649\" will also return false because -2147483649 is smaller than -2147483648.

public static boolean isInt(String s) {
  if(s == null) {
    return false;
  }
  s = s.trim(); //Don\'t get tricked by whitespaces.
  int len = s.length();
  if(len == 0) {
    return false;
  }
  //The bottom limit of an int is -2147483648 which is 11 chars long.
  //[note that the upper limit (2147483647) is only 10 chars long]
  //Thus any string with more than 11 chars, even if represents a valid integer, 
  //it won\'t fit in an int.
  if(len > 11) {
    return false;
  }
  char c = s.charAt(0);
  int i = 0;
  //I don\'t mind the plus sign, so \"+13\" will return true.
  if(c == \'-\' || c == \'+\') {
    //A single \"+\" or \"-\" is not a valid integer.
    if(len == 1) {
      return false;
    }
    i = 1;
  }
  //Check if all chars are digits
  for(; i < len; i++) {
    c = s.charAt(i);
    if(c < \'0\' || c > \'9\') {
      return false;
    }
  }
  //If we reached this point then we know for sure that the string has at
  //most 11 chars and that they\'re all digits (the first one might be a \'+\'
  // or \'-\' thought).
  //Now we just need to check, for 10 and 11 chars long strings, if the numbers
  //represented by the them don\'t surpass the limits.
  c = s.charAt(0);
  char l;
  String limit;
  if(len == 10 && c != \'-\' && c != \'+\') {
    limit = \"2147483647\";
    //Now we are going to compare each char of the string with the char in
    //the limit string that has the same index, so if the string is \"ABC\" and
    //the limit string is \"DEF\" then we are gonna compare A to D, B to E and so on.
    //c is the current string\'s char and l is the corresponding limit\'s char
    //Note that the loop only continues if c == l. Now imagine that our string
    //is \"2150000000\", 2 == 2 (next), 1 == 1 (next), 5 > 4 as you can see,
    //because 5 > 4 we can guarantee that the string will represent a bigger integer.
    //Similarly, if our string was \"2139999999\", when we find out that 3 < 4,
    //we can also guarantee that the integer represented will fit in an int.
    for(i = 0; i < len; i++) {
      c = s.charAt(i);
      l = limit.charAt(i);
      if(c > l) {
        return false;
      }
      if(c < l) {
        return true;
      }
    }
  }
  c = s.charAt(0);
  if(len == 11) {
    //If the first char is neither \'+\' nor \'-\' then 11 digits represent a 
    //bigger integer than 2147483647 (10 digits).
    if(c != \'+\' && c != \'-\') {
      return false;
    }
    limit = (c == \'-\') ? \"-2147483648\" : \"+2147483647\";
    //Here we\'re applying the same logic that we applied in the previous case
    //ignoring the first char.
    for(i = 1; i < len; i++) {
      c = s.charAt(i);
      l = limit.charAt(i);
      if(c > l) {
        return false;
      }
      if(c < l) {
        return true;
      }
    }
  }
  //The string passed all tests, so it must represent a number that fits
  //in an int...
  return true;
}


回答23:

is_number = true;
try {
  Integer.parseInt(mystr)
} catch (NumberFormatException  e) {
  is_number = false;
}


回答24:

What you did works, but you probably shouldn\'t always check that way. Throwing exceptions should be reserved for \"exceptional\" situations (maybe that fits in your case, though), and are very costly in terms of performance.



回答25:

Number number;
try {
    number = NumberFormat.getInstance().parse(\"123\");
} catch (ParseException e) {
    //not a number - do recovery.
    e.printStackTrace();
}
//use number


回答26:

This would work only for positive integers.

public static boolean isInt(String str) {
    if (str != null && str.length() != 0) {
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i))) return false;
        }
    }
    return true;        
}


回答27:

This works for me. Simply to identify whether a String is a primitive or a number.

private boolean isPrimitive(String value){
        boolean status=true;
        if(value.length()<1)
            return false;
        for(int i = 0;i<value.length();i++){
            char c=value.charAt(i);
            if(Character.isDigit(c) || c==\'.\'){

            }else{
                status=false;
                break;
            }
        }
        return status;
    }


回答28:

To check for all int chars, you can simply use a double negative.

if (!searchString.matches(\"[^0-9]+$\")) ...

[^0-9]+$ checks to see if there are any characters that are not integer, so the test fails if it\'s true. Just NOT that and you get true on success.



回答29:

Find this may helpful:

public static boolean isInteger(String self) {
    try {
        Integer.valueOf(self.trim());
        return true;
    } catch (NumberFormatException nfe) {
        return false;
    }
}


回答30:

I believe there\'s zero risk running into an exception, because as you can see below you always safely parse int to String and not the other way around.

So:

  1. You check if every slot of character in your string matches at least one of the characters {\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\"}.

    if(aString.substring(j, j+1).equals(String.valueOf(i)))
    
  2. You sum all the times that you encountered in the slots the above characters.

    digits++;
    
  3. And finally you check if the times that you encountered integers as characters equals with the length of the given string.

    if(digits == aString.length())
    

And in practice we have:

    String aString = \"1234224245\";
    int digits = 0;//count how many digits you encountered
    for(int j=0;j<aString.length();j++){
        for(int i=0;i<=9;i++){
            if(aString.substring(j, j+1).equals(String.valueOf(i)))
                    digits++;
        }
    }
    if(digits == aString.length()){
        System.out.println(\"It\'s an integer!!\");
        }
    else{
        System.out.println(\"It\'s not an integer!!\");
    }

    String anotherString = \"1234f22a4245\";
    int anotherDigits = 0;//count how many digits you encountered
    for(int j=0;j<anotherString.length();j++){
        for(int i=0;i<=9;i++){
            if(anotherString.substring(j, j+1).equals(String.valueOf(i)))
                    anotherDigits++;
        }
    }
    if(anotherDigits == anotherString.length()){
        System.out.println(\"It\'s an integer!!\");
        }
    else{
        System.out.println(\"It\'s not an integer!!\");
    }

And the results are:

It\'s an integer!!

It\'s not an integer!!

Similarly, you can validate if a String is a float or a double but in those cases you have to encounter only one . (dot) in the String and of course check if digits == (aString.length()-1)

Again, there\'s zero risk running into a parsing exception here, but if you plan on parsing a string that it is known that contains a number (let\'s say int data type) you must first check if it fits in the data type. Otherwise you must cast it.

I hope I helped



标签: java string int