如何避免Java中的数字格式异常?

15

在我日常的Web应用程序开发中,有许多情况需要从用户那里获取一些数字输入。

然后将这个数字输入传递给应用程序的服务层或DAO层。

由于它是一个数字(整数或浮点数),因此在某个阶段我们需要将其转换为整数,如下面的代码片段所示。

String cost = request.getParameter("cost");

if (cost !=null && !"".equals(cost) ){
    Integer intCost = Integer.parseInt(cost);
    List<Book> books = bookService . findBooksCheaperThan(intCost);  
}

在上述情况下,我需要检查输入是否为空或者没有输入(空白),有时也可能存在非数字输入的可能性,例如blah,test等。

如何处理这种情况的最佳方式是什么?


2
如果(cost != null && !"".equals(cost)),则为if (!"".equals(cost)) ;) - Peter Lawrey
4
@peter-lawrey:不正确。如果cost == null怎么办? 这两个检查是不同的。 前一个表达式将返回false,后一个将返回true。 - occulus
什么是NumberFormatException? - xenteros
13个回答

27

只需捕获异常并进行正确的异常处理:

if (cost !=null && !"".equals(cost) ){
        try {
           Integer intCost = Integer.parseInt(cost);
           List<Book> books = bookService . findBooksCheaperThan(intCost);  
        } catch (NumberFormatException e) {
           System.out.println("This is not a number");
           System.out.println(e.getMessage());
        }
    }

+1:我会在错误信息中添加 e.getMessage()。 - Peter Lawrey
是的,sysout只是一个例子。无论如何,他必须进行适当的异常处理。但我会添加它。谢谢。 - RoflcoptrException
2
+1:终于有一个明智的答案了。我唯一想改变的是将对bookService的调用移出try块,因为这个特定的异常只适用于解析cost字符串。 - Alexander Pogrebnyak
@Alexander Pogrebnyak 是的,没错,但我认为这取决于 OP 想要什么样的逻辑。他不能不花费就找到一本更便宜的书。(顺便说一下...你和德甲职业足球运动员有同样的名字 :D) - RoflcoptrException
我们真的应该在调用 parseInt(String) 之前像 StringUtils.isNumeric(String) 这样进行检查吗? - Learner
我只是好奇NumberFormatException是否为未经检查的异常,如果我们应该尝试避免抛出异常并像Learner所说的那样防止它。 - Andrew S

4

像往常一样,雅加达公共库至少有部分答案:

NumberUtils.isNumber()

这可以用来检查大多数情况下给定的字符串是否是数字。但是如果您的字符串不是数字,仍然需要选择要执行的操作...


由于这个问题在谷歌搜索“NumberFormatException”时排名第一,而您提供的链接似乎已经失效,因此在此提供一个替代方案。使用NumberUtils.isNumber()方法。目前该方法仍然可用,但已被弃用,请谨慎使用。 - cluuu

2

最近版本的Java中的异常并不昂贵,因此避免它们并不重要。使用人们建议的try/catch块;如果您在过程早期(即用户输入后)尽早捕获异常,则后续过程中就不会出现问题(因为它仍然是正确的类型)。

异常曾经比现在昂贵得多;在您知道异常实际上正在引起问题之前,请不要为性能进行优化(在这里它们不会引起问题)。


1
public class Main {
    public static void main(String[] args) {

        String number;

        while(true){

            try{
                number = JOptionPane.showInputDialog(null);

                if( Main.isNumber(number) )
                    break;

            }catch(NumberFormatException e){
                System.out.println(e.getMessage());
            }

        }

        System.out.println("Your number is " + number);

    }

    public static boolean isNumber(Object o){
        boolean isNumber = true;

        for( byte b : o.toString().getBytes() ){
            char c = (char)b;
            if(!Character.isDigit(c))
                isNumber = false;
        }

        return isNumber;
    }

}

1

来自Apache Commons Lang的方法文档(从这里):

Checks whether the String a valid Java number.

Valid numbers include hexadecimal marked with the 0x qualifier, scientific notation and numbers marked with a type qualifier (e.g. 123L).

Null and empty String will return false.

Parameters:

`str` - the `String` to check

Returns:

`true` if the string is a correctly formatted number

isNumber 来自 java.org.apache.commons.lang3.math.NumberUtils

public static boolean isNumber(final String str) {
    if (StringUtils.isEmpty(str)) {
        return false;
    }
    final char[] chars = str.toCharArray();
    int sz = chars.length;
    boolean hasExp = false;
    boolean hasDecPoint = false;
    boolean allowSigns = false;
    boolean foundDigit = false;
    // deal with any possible sign up front
    final int start = (chars[0] == '-') ? 1 : 0;
    if (sz > start + 1 && chars[start] == '0' && chars[start + 1] == 'x') {
        int i = start + 2;
        if (i == sz) {
            return false; // str == "0x"
        }
        // checking hex (it can't be anything else)
        for (; i < chars.length; i++) {
            if ((chars[i] < '0' || chars[i] > '9')
                && (chars[i] < 'a' || chars[i] > 'f')
                && (chars[i] < 'A' || chars[i] > 'F')) {
                return false;
            }
        }
        return true;
    }
    sz--; // don't want to loop to the last char, check it afterwords
          // for type qualifiers
    int i = start;
    // loop to the next to last char or to the last char if we need another digit to
    // make a valid number (e.g. chars[0..5] = "1234E")
    while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) {
        if (chars[i] >= '0' && chars[i] <= '9') {
            foundDigit = true;
            allowSigns = false;

        } else if (chars[i] == '.') {
            if (hasDecPoint || hasExp) {
                // two decimal points or dec in exponent   
                return false;
            }
            hasDecPoint = true;
        } else if (chars[i] == 'e' || chars[i] == 'E') {
            // we've already taken care of hex.
            if (hasExp) {
                // two E's
                return false;
            }
            if (!foundDigit) {
                return false;
            }
            hasExp = true;
            allowSigns = true;
        } else if (chars[i] == '+' || chars[i] == '-') {
            if (!allowSigns) {
                return false;
            }
            allowSigns = false;
            foundDigit = false; // we need a digit after the E
        } else {
            return false;
        }
        i++;
    }
    if (i < chars.length) {
        if (chars[i] >= '0' && chars[i] <= '9') {
            // no type qualifier, OK
            return true;
        }
        if (chars[i] == 'e' || chars[i] == 'E') {
            // can't have an E at the last byte
            return false;
        }
        if (chars[i] == '.') {
            if (hasDecPoint || hasExp) {
                // two decimal points or dec in exponent
                return false;
            }
            // single trailing decimal point after non-exponent is ok
            return foundDigit;
        }
        if (!allowSigns
            && (chars[i] == 'd'
                || chars[i] == 'D'
                || chars[i] == 'f'
                || chars[i] == 'F')) {
            return foundDigit;
        }
        if (chars[i] == 'l'
            || chars[i] == 'L') {
            // not allowing L with an exponent or decimal point
            return foundDigit && !hasExp && !hasDecPoint;
        }
        // last character is illegal
        return false;
    }
    // allowSigns is true iff the val ends in 'E'
    // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass
    return !allowSigns && foundDigit;
}

[代码采用Apache许可证的第二版]


1
一种可能性是:捕获异常并在用户前端显示错误消息。
编辑:在GUI中为字段添加监听器,并在那里检查用户输入,使用此解决方案,异常情况应该非常少见...

1
异常应该只在特殊情况下使用,而不是在正常的预期情况下使用,比如“没有输入任何内容”。设置异常的成本很高。 - occulus
1
真的,但只要我们在Java中没有得到输出参数,你几乎无法绕过(正则表达式方法有缺陷!)。此外,为什么异常会“设置”成昂贵的呢?它只是异常表中的一个条目,因此只要您不走异常代码路径(这应该很少见),就没有真正的性能问题。 - Voo
认为Voo是正确的,否则你如何检查这个? - Tobias
抱歉,你说的“you hardly get around”是什么意思?还有,“get out parameters”是什么意思? - occulus
"hardly get around [using the exception]" it should be. 正确的正则表达式方法需要一个详尽的数字列表(所有小于10位数的数字都可以,然后是2000000000|2000000001|..|2147483647)。输出参数显然是最好的解决方案,即获得签名布尔值tryParseInt(int val, out int erg) - 请参阅使用此功能的一种语言C#。 - Voo

1

我建议做两件事情:

  • 在将输入传递给Servlet之前,在客户端对其进行验证
  • 捕获异常并在用户前端显示错误消息,就像Tobiask所提到的那样。这种情况通常不会发生,但永远不要相信你的客户端。 ;-)

1
在我看来,客户端验证只能作为验证过程的一部分,但不能完全依赖它(除非你想打开各种攻击模式的窗口)... - Jan Groth
这就是我说的:永远不要相信你的客户端。尽管如此,客户端也应该进行检查。仅在服务器端进行简单的输入验证并不是非常有效的。正如其他人所提到的,可能有框架/库可以帮助您解决这个问题。甚至可能是新的Bean Validation(Java EE 6)?(我还没有测试过。) - Puce

0

判断字符串是整数还是浮点数,并以更长的格式表示。

整数

 String  cost=Long.MAX_VALUE+"";
  if (isNumeric (cost))    // returns false for non numeric
  {  
      BigInteger bi  = new BigInteger(cost);

  }

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

重申一遍:正则表达式将允许像“9999999999999999999999”这样的字符串,但解析器会抛出异常。浮点数正则表达式也是如此。 - Voo
@Voo:对于上面的代码,9999999999999999999999可以正常工作。如果我错了,请告诉我。 - Dead Programmer
1
你的代码允许9999999999999999999999作为输入,如果你尝试在if语句中解析它,将会抛出异常。真正的问题不仅仅是“这个字符串是一个数字吗?”,而是“这个字符串是一个数字并且可以表示为整数吗?” - Voo

0
你可以通过使用Scanner类来避免看起来不舒服的try/catch或正则表达式。
String input = "123";
Scanner sc = new Scanner(input);
if (sc.hasNextInt())
    System.out.println("an int: " + sc.nextInt());
else {
    //handle the bad input
}

Scanner在内部使用Integer.parseInt,因此无法避免try/catch,但可以将它们悄悄地隐藏起来。 - Danubian Sailor
有趣...我没想过去看源代码。 在Scanner.hasNext中,他们将字符串缓冲区与整数模式匹配,因此我不确定是否有办法使其实际上抛出nfe,即使在内部也是如此。如果您已经检查了hasNextInt(),那么nextInt()也应该是安全的。 - nairbv
@DanubianSailor:至少你在代码中避免了try/catch。使用布尔值处理问题更加优雅。 - Anna

0

尝试将奖品转换为十进制格式...

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Bigdecimal {
    public static boolean isEmpty (String st) {
        return st == null || st.length() < 1; 
    }
    public static BigDecimal bigDecimalFormat(String Preis){        
        //MathContext   mi = new MathContext(2);
        BigDecimal bd = new BigDecimal(0.00);

                         bd = new BigDecimal(Preis);


            return bd.setScale(2, RoundingMode.HALF_UP);

        }
    public static void main(String[] args) {
        String cost = "12.12";
        if (!isEmpty(cost) ){
            try {
               BigDecimal intCost = bigDecimalFormat(cost);
               System.out.println(intCost);
               List<Book> books = bookService.findBooksCheaperThan(intCost);  
            } catch (NumberFormatException e) {
               System.out.println("This is not a number");
               System.out.println(e.getMessage());
            }
        }

}
}

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