检测字符串是否为数字的最优雅方法是什么?

8

有没有比以下方法更好、更优雅(或者可能更快)的方法:

boolean isNumber = false;
try{
   Double.valueOf(myNumber);
   isNumber = true;
} catch (NumberFormatException e) {
}

编辑: 由于我不能选择两个答案,所以我选择正则表达式的答案,因为a)它很优雅,b)说“Jon Skeet解决了问题”是一种重言,因为Jon Skeet本身就是所有问题的解决方案。


这是我在需要用户输入数字时的做法。然而,我从未遇到过其他情况。 - Chris Serra
我之前问过一个非常类似的问题。其中一些答案可能会有所帮助。https://dev59.com/AnVC5IYBdhLWcg3wnCj6 - Bill the Lizard
Jajajajaja好答案Epaga。 - Agusti-N
11个回答

10

我不相信Java内置了任何可以更快、更可靠地进行此操作的内容,假设您稍后想要使用Double.valueOf(或类似方法)实际解析它。

我将使用Double.parseDouble而不是Double.valueOf来避免不必要地创建Double,并且您还可以通过在前面检查数字、e/E、 - 和. 来更快地消除明显愚蠢的数字。所以,代码应该像这样:

public boolean isDouble(String value)
{        
    boolean seenDot = false;
    boolean seenExp = false;
    boolean justSeenExp = false;
    boolean seenDigit = false;
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (c >= '0' && c <= '9')
        {
            seenDigit = true;
            continue;
        }
        if ((c == '-' || c=='+') && (i == 0 || justSeenExp))
        {
            continue;
        }
        if (c == '.' && !seenDot)
        {
            seenDot = true;
            continue;
        }
        justSeenExp = false;
        if ((c == 'e' || c == 'E') && !seenExp)
        {
            seenExp = true;
            justSeenExp = true;
            continue;
        }
        return false;
    }
    if (!seenDigit)
    {
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}

请注意,尽管尝试了几次,但此代码仍未涵盖“NaN”或十六进制值。是否允许这些值通过取决于上下文。

根据我的经验,正则表达式比上述硬编码检查要慢。


1
@Paul:确实。此时,您基本上已经实现了.NET的“bool Double.TryParse(string text,out double)”。在Java中,另一种选择是返回Double(对象)或“无效”的null。 - Jon Skeet
嘘...+1.0e-7是一个有效的双精度浮点数。 - plinth
这样你就可以得到一个便宜、简单的预过滤器,它有误报而不是一个便宜但复杂的预过滤器,它有误拒。 - plinth
是的,我认为你是对的。不过我已经修复了可能的符号位(在看到你的评论之前 :) - Jon Skeet
我这么假设是因为Double.valueOf确实会这样做(如果我没记错的话)。 - Jon Skeet
显示剩余5条评论

9
你可以使用正则表达式,例如String.matches("^[\\d\\-\\.]+$");(如果你不需要测试负数或浮点数,可以简化一些)。
不确定这是否比你概述的方法更快。
编辑:鉴于所有这些争议,我决定进行一项测试,并获得有关每种方法运行速度的数据。不是正确性,只是它们运行得有多快。
你可以阅读我的结果在我的博客上。(提示:Jon Skeet FTW)。

它也会对负数和非整数失败 :) - Jon Skeet
这个答案和John Skeet的答案都假设小数点是“.”,并且不允许使用千位分隔符。 - John Gardner
正则表达式的速度通常不如非正则表达式,但这种差异不一定会有影响。然而,这个答案中的正则表达式——^[\d|\-|\.]+$——甚至都不正确。例如,它将 "-"、"." 和 "|" 视为有效数字。 - Alan Moore
@Phill:即使你不是,NumberFormat 更容易使用,已经调试了其正则表达式,并由 Sun 维护。如果已经存在一个规范的解决方案,为什么要创建第二个解决方案呢? - Ran Biron
因为你可能提前知道你将获得的数字格式。看起来,我完全同意DRY原则和不重复发明轮子,只是觉得这可能是在特定情况下快速检查数字的一种简单粗暴的方式。并不是一直适用。我应该把这一点表达得更清楚一些。 - user7094
显示剩余4条评论

8
请参见java.text.NumberFormat(javadoc)。
NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
Number myNumber = nf.parse(myString);
int myInt = myNumber.intValue();
double myDouble = myNumber.doubleValue();

1
很高兴看到一个不做本地化假设的答案。+1 - J c
全球社区 - 您必须具备适当的地区格式化/解析。在德国,1.024,00是巴黎的1 024.00和纽约的1,024.00。 - Ran Biron

5

实际上,在Double javadocs中已经给出了正确的正则表达式:

为了避免在无效字符串上调用此方法并抛出NumberFormatException,可以使用以下正则表达式来筛选输入字符串:

    final String Digits     = "(\\p{Digit}+)";
    final String HexDigits  = "(\\p{XDigit}+)";
    // an exponent is 'e' or 'E' followed by an optionally 
    // signed decimal integer.
    final String Exp        = "[eE][+-]?"+Digits;
    final String fpRegex    =
        ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
         "[+-]?(" + // Optional sign character
         "NaN|" +           // "NaN" string
         "Infinity|" +      // "Infinity" string

         // A decimal floating-point string representing a finite positive
         // number without a leading sign has at most five basic pieces:
         // Digits . Digits ExponentPart FloatTypeSuffix
         // 
         // Since this method allows integer-only strings as input
         // in addition to strings of floating-point literals, the
         // two sub-patterns below are simplifications of the grammar
         // productions from the Java Language Specification, 2nd 
         // edition, section 3.10.2.

         // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
         "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

         // . Digits ExponentPart_opt FloatTypeSuffix_opt
         "(\\.("+Digits+")("+Exp+")?)|"+

   // Hexadecimal strings
   "((" +
    // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "(\\.)?)|" +

    // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

    ")[pP][+-]?" + Digits + "))" +
         "[fFdD]?))" +
         "[\\x00-\\x20]*");// Optional trailing "whitespace"

    if (Pattern.matches(fpRegex, myString))
        Double.valueOf(myString); // Will not throw NumberFormatException
    else {
        // Perform suitable alternative action
    }

然而,这并不允许本地化表示:

要解释浮点值的本地化字符串表示,请使用NumberFormat的子类。


3
使用Apache Commons中的StringUtils.isDouble(String)

已移至更新版本。请参见org.apache.commons.lang3.math.NumberUtils。特别是NumberUtils.isParseable()和NumberUtils.isCreatable()。 - David Leppik

3
利用Skeet先生的经验:
private boolean IsValidDoubleChar(char c)
{
    return "0123456789.+-eE".indexOf(c) >= 0;
}

public boolean isDouble(String value)
{
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (IsValidDoubleChar(c))
            continue;
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}

尝试使用 1 时,结果为 false,但应该是 true :P - test

2

commons-lang 还可以检测到 Java 无法检测到的一些东西,比如:多个逗号、数字周围的空格等等。 - Aaron Digulla
这取决于你想做什么。有些数字无法表示为Java double - 是否应该包含它们?如果您真的想知道是否最终能够转换为Java double,我怀疑在没有调用Double的情况下可靠地进行此操作会很困难。 - Jon Skeet

2
大多数答案都是比较可接受的解决方案。所有的正则表达式解决方案都存在一个问题,即不能正确处理您关心的所有情况。
如果您真的想确保字符串是有效的数字,则应使用自己的解决方案。不要忘记,我想,大多数情况下字符串将是有效的数字,并且不会引发异常。因此,大多数时候性能与Double.valueOf()相同。
我想这并不是一个答案,只是验证了您最初的直觉。
兰迪

我喜欢正则表达式的方法。虽然所提出的方法可能不符合OP的要求,但这只是因为要求不明确。呈现解决方案的方式是我们所能做的最好的事情。 - Brian Knoblauch
我明白你的意思。如果目标只是检测数字,那么[0-9]+就可以了。 - Randy Stegbauer

1

跟随 Phill 的回答,我可以建议另一个正则表达式吗?

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

1
我更喜欢使用循环遍历字符串的char[]表示,然后使用Character.isDigit()方法。如果希望代码简洁优雅,我认为这是最易读的方式。
package tias;

public class Main {
  private static final String NUMERIC = "123456789";
  private static final String NOT_NUMERIC = "1L5C";

  public static void main(String[] args) {
    System.out.println(isStringNumeric(NUMERIC));
    System.out.println(isStringNumeric(NOT_NUMERIC));
  }

  private static boolean isStringNumeric(String aString) {
    if (aString == null || aString.length() == 0) {
      return false;
    }
    for (char c : aString.toCharArray() ) {
      if (!Character.isDigit(c)) {
        return false;
      }
    }
    return true;
  }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接