当我尝试使用德语语言环境将字符串“3.14”读取为浮点数时,我预计会发生以下两种情况之一:
(1) 抛出错误,因为这不是用德语书写 3.14 的有效方法
(2) 回退到更标准的十进制表示法并将数字读为 3.14,因为这是任何德国人都会读到的数字
但是我得到的是 314。
import java.text.NumberFormat;
import java.util.Locale;
public class MyClass {
public static void main(String args[]) throws Exception {
System.out.println(
NumberFormat.getNumberInstance(Locale.GERMANY).parse("3.14")
); // prints 314
}
}
parse
的
oracle 文档指出:
数字解析(字符串源)
从给定字符串的开头解析文本以生成数字。
这并不能真正解释我在这里看到的内容,因为它没有指定任何不愉快的路径。 javas 对德国十进制数的理解是什么?如何快速失败并安全地将字符串转换为采用德国十进制表示法的数字?
您面临的问题是因为 .在德语语言环境中被视为分组分隔符:
上一张图像是从 DecimalFormat.java 类中提取的
之后,如果解析发现分组字符就忽略它:
} else if (!isExponent && ch == grouping && isGroupingUsed()) {
if (sawDecimal) {
break;
}
// Ignore grouping characters, if we are using them, but
// require that they be followed by a digit. Otherwise
// we backup and reprocess them.
backup = position;
}
在你问之前,
sawDecimal
是false
,并且备份最初在循环开始时为-1,在找到下一个数字1时为-1。所以,backup = position
;没有做任何事情。
NumberFormat 将验证其输入的基本假设是错误的。现代开发人员可能期望进行验证,特别是因为该方法会抛出
ParseException
作为已检查的异常,但借助开源的魔力,我可以查看源代码并意识到我错了,这段 Java 1.1 代码是用与我习惯的设计原则不同。
我们在这里使用的具体类中的关键代码部分(对于一种实现)位于 openjdk > DecimalFormat.java > int subparseNumber 中,其中输入字符串被转换为“DigitList”。具有德语语言环境的“3.14”的数字列表确实是
[3, 1, 4]
,因为千位分隔符确实被忽略,正如 @GiacomoCatenazzi 在他的评论 1 中指出的那样,因此后续代码必须将其解释为 314。此外,当遇到无效字符,解析就会停止,例如“0x134”-> 0,没有错误。
从源代码中可以了解更多内容:NumberFormat 不是线程安全的,您不能在多个线程中重用同一实例。现代假设认为像
format.parse(input) -> obj
这样的函数是非常安全的,因为输入和格式只能只读访问,但这种假设并不成立 - 解析会更改 NumberFormat 实例的内部状态。您只能在 parse
完成后重用该实例。
那么我如何做在Java中将字符串快速失败转换为数字?
(1) 如果您知道目标类型并且数字采用标准十进制格式,则此方法有效:
Float.valueOf("3,14"); // NumberFormatException
Float.valueOf("3.14"); // 3.14f
请注意,
NumberFormat.getNumberInstance().parse("3,14")
将返回 314 - 不是错误 - 因此这个无验证问题绝不是德语区域设置所独有的。
(2) 如果我必须使用 German-locale-number-strings 来读取数字,我必须检查输入字符串是否与期望匹配,并且 NumberFormat 没有提供任何方法来做到这一点,似乎也没有对这个 12 年老问题的满意的快速失败/非 gigo 答案:Convert String with Dot or Comma to Float Number
我最好的想法是自己验证输入并以这种方式限制它。这是一个比必要的更严格的解决方案:
if (inputString.contains(".")) {
// throw
}
return Float.valueOf(inputString.replace(',', '.'));
1 你实际上可以做
format.setGroupingUsed(false)
,然后你可以将“3.14”解析为3而不是314,所以它们被完全忽略并不完全正确。但是没有代码使用分组字符来判断输入字符串的正确性,即使有 format.setGroupingSize
和 getter 控制应该将多少个数字分组在一起。