如何在 Java 中检查字符串是否为数字

在解析字符串之前,如何检查字符串是否为数字?

答案

通常,这是通过简单的用户定义函数(即,自带的 “isNumeric” 函数)完成的。

就像是:

public static boolean isNumeric(String str) { 
  try {  
    Double.parseDouble(str);  
    return true;
  } catch(NumberFormatException e){  
    return false;  
  }  
}

但是,如果您要多次调用此函数,并且希望许多检查由于不为数字而失败,则该机制的性能将不佳,因为您依赖于每次失败都引发异常,这是一个相当昂贵的操作。

一种替代方法是使用正则表达式检查作为数字的有效性:

public static boolean isNumeric(String str) {
  return str.matches("-?\\d+(\\.\\d+)?");  //match a number with optional '-' and decimal.
}

但是,请谨慎使用上述 RegEx 机制,因为如果您使用非阿拉伯数字(即 0 到 9 以外的数字),它将失败。这是因为 RegEx 的 “\ d” 部分仅与 [0-9] 匹配,实际上并没有国际上的数字意识。 (感谢 OregonGhost 指出这一点!)

甚至另一种替代方法是使用 Java 的内置 java.text.NumberFormat 对象查看在解析字符串之后解析器位置是否在字符串的末尾。如果是这样,我们可以假设整个字符串是数字:

public static boolean isNumeric(String str) {
  NumberFormat formatter = NumberFormat.getInstance();
  ParsePosition pos = new ParsePosition(0);
  formatter.parse(str, pos);
  return str.length() == pos.getIndex();
}

使用Apache Commons Lang 3.5 及更高版本: NumberUtils.isCreatableStringUtils.isNumeric

使用Apache Commons Lang 3.4 及更低版本: NumberUtils.isNumberStringUtils.isNumeric

您还可以使用StringUtils.isNumericSpace ,它对空字符串返回true ,并忽略字符串中的内部空格。另一种方法是使用NumberUtils.isParsable ,它基本上根据 Java 检查数字是否可解析。 (链接的 javadocs 包含每种方法的详细示例。)

如果您使用的是 Android,则应使用:

android.text.TextUtils.isDigitsOnly(CharSequence str)

文档可以在这里找到

保持简单 。大多数人都可以 “重新编程”(同一件事)。

正如 @CraigTP 在他的出色回答中提到的那样,在使用 Exceptions 测试字符串是否为数字时,我也有类似的性能问题。所以我最终将字符串拆分并使用java.lang.Character.isDigit()

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

根据JavadocCharacter.isDigit(char)将正确识别非拉丁数字。从性能角度来看,我认为简单的 N 个比较(其中 N 是字符串中的字符数)会比进行正则表达式匹配更具计算效率。

更新:正如 Jean-FrançoisCorbett 在评论中指出的那样,以上代码仅会验证正整数,这涵盖了我的大部分用例。下面是更新的代码,该代码根据系统中使用的默认语言环境正确验证十进制数字,并假定十进制分隔符在字符串中仅出现一次。

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;
}

Java 8 Lambda 表达式。

String someString = "123123";
boolean isNumeric = someString.chars().allMatch( Character::isDigit );

Google 的 Guava 库提供了一个很好的辅助方法: Ints.tryParse 。您可以像Integer.parseInt一样使用它,但是它会返回null而不是在字符串没有解析为有效整数的情况下抛出 Exception。请注意,它返回的是 Integer,而不是 int,因此您必须将其转换 / 自动装箱为 int。

例:

String s1 = "22";
String s2 = "22.2";
Integer oInt1 = Ints.tryParse(s1);
Integer oInt2 = Ints.tryParse(s2);

int i1 = -1;
if (oInt1 != null) {
    i1 = oInt1.intValue();
}
int i2 = -1;
if (oInt2 != null) {
    i2 = oInt2.intValue();
}

System.out.println(i1);  // prints 22
System.out.println(i2);  // prints -1

但是,从当前版本(Guava r11)开始,它仍标记为 @Beta。

我还没有进行基准测试。查看源代码,会进行很多健全性检查,但会产生一些开销,但最终它们使用了Character.digit(string.charAt(idx)) ,与上面 @Ibrahim 的答案类似但略有不同。在其实现的幕后,没有异常处理开销。

不要使用 “例外” 来验证您的值。改用 Util 库,例如 apache NumberUtils:

NumberUtils.isNumber(myStringValue);

编辑

请注意,如果您的字符串以 0 开头,NumberUtils 会将您的值解释为十六进制。

NumberUtils.isNumber("07") //true
NumberUtils.isNumber("08") //false

为什么每个人都在寻求例外 / 正则表达式解决方案?

虽然我可以理解大多数人都可以使用 try / catch,但是如果您想经常这样做,可能会非常麻烦。

我在这里所做的就是使用正则表达式,parseNumber()方法和数组搜索方法来查看哪种方法最有效。这次,我只看了整数。

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;
}

我得到的速度结果是:

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

免责声明:我并不是说这些方法都是 100%优化的,它们只是为了演示数据

当且仅当数字为 4 个字符或更少且每个字符串始终为数字时,才会赢得例外... 在这种情况下,为什么还要进行检查?

简而言之,如果您经常使用 try / catch 遇到无效数字,这将非常痛苦。我始终遵循的重要规则是, 切勿对程序流使用 try / catch 。这是一个示例。

有趣的是,如果 char <0 ||> 9 的编写极其简单,容易记住(并且应该以多种语言工作),并且赢得了几乎所有的测试场景。

唯一的缺点是我猜测 Integer.parseInt()可能会处理非 ASCII 数字,而数组搜索方法则无法。


对于那些想知道为什么我说容易记住一个字符数组的人,如果您知道没有负号,则可以轻松地摆脱一些浓缩的东西,如下所示:

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;

最后,最后一点是,我对接受的示例中的分配操作员充满了好奇。添加的分配

double d = Double.parseDouble(...)

不仅没有用,因为您甚至不使用该值,而且浪费了处理时间,并使运行时间增加了几纳秒(这导致测试增加了 100-200 毫秒)。我看不到为什么有人会这样做,因为这实际上是降低性能的额外工作。

您可能会认为它会被优化…… 尽管也许我应该检查字节码并查看编译器在做什么。但这并不能解释为什么它总是对我来说显得更长,尽管它以某种方式进行了优化…… 因此我想知道发生了什么。需要注意的是:更长的意思是,我将测试运行 10000000 次迭代,并且多次运行该程序(10x +)总是表明测试速度较慢。

编辑:更新了 Character.isDigit()的测试

public static boolean isNumeric(String str)
{
    return str.matches("-?\\d+(.\\d+)?");
}

CraigTP 的正则表达式(如上所示)会产生一些误报。例如,“23y4” 将被计为数字,因为 “。” 匹配任何非小数点的字符。

此外,它还会拒绝任何以 “+” 开头的数字

避免这两个小问题的替代方法是

public static boolean isNumeric(String str)
{
    return str.matches("[+-]?\\d*(\\.\\d+)?");
}

您可以使用NumberFormat#parse

try
{
     NumberFormat.getInstance().parse(value);
}
catch(ParseException e)
{
    // Not a number.
}