这一点在很早之前也想过,当时看到很多电商的接口返回的货币都是以“分”为单位,为就想以“元”为单位不是更合理,更加符合人的思维逻辑吗?那就看下下面的案例。
小明去超市用5元纸币买一瓶 4.6元的饮料,售货员应该找你 0.4元,可下面这个程序打印出来的结果却不尽人意,输出了 0.40000000000000036。
public class Proposal_22 {
public static void main(String[] args) {
System.out.println(5.00 - 4.60);
}
}
其实原因是计算机世界里,浮点数不一定准确,0.4 这个十进制转换成二进制小数使用“乘 2 取整,顺序排列”法,最后发现 0.4 无法使用二进制准确表示,在二进制世界里他是一个无限循环的小数。
那行,那我给他取整不久完事了?
public class Proposal_22 {
public static void main(String[] args) {
NumberFormat f = new DecimalFormat("#.##");
System.out.println(f.format(5.00 - 4.60));
}
}
这个时候打印出来的是 0.4 没错,看似解决了问题,但是在大批量计算中结果就会有很大差距,如银行会计系统,要的就是精准,决不允许这种错误,这里解决办法也有:
1.使用 BIgDecimal:专门弥补浮点数无法精确计算的缺憾而设计;
2.使用整型:把参数与运算的值扩大 100 倍,在零售行业一般应用较多,运算简单。
最近华为出了一款非常火爆的折叠屏手机 MetaX,假设这个手机要提前一个月预定,并且它规定了一个渠道商最多能采购的产品数量,后台逻辑如下:
public class Proposal_24 {
// 一个渠道商最多可以购买的数量
public final static int LIMIT = 2000;
public static void main(String[] args) {
// 当前渠道商拥有的手机数量
int cur = 1000;
Scanner scanner = new Scanner(System.in);
System.out.println("请输入需要预定的数量:");
while (scanner.hasNextInt()) {
int order = scanner.nextInt();
// 当前拥有的与准备订购的产品数量之和
if (order > 0 && order + cur <= LIMIT) {
System.out.println("你已经成功预定了" + order + "个产品");
} else {
System.out.println("超过限额,预定失败");
}
}
}
}
逻辑看似很简单,不一一解读了,尝试了几个测试结果如下:
前两个测试结果很正常,一个是没有超限,另一个是超限的,但是最后这个结果有点奇怪,照理来说肯定超限,2147483647 这个数字眼熟吗?没错,是 Java 中 int 的最大取值,那当前 order 为该值,再加上 1000,结果为 -2147482649,这个结果肯定小于正数 2000 了,这类问题就是数字越界使校验条件失效,在 WEB 开发中无论前端还是后端务必校验用户提交的数据。
看到这个建议,可能会有疑惑,Java 的覆写本身是针对非静态方法的,不能针对静态方法,它虽然不能被覆写,但是却能被隐藏:
public class Proposal_33 {
public static void main(String[] args) {
Base base = new Sub();
// 调用非静态方法
base.doAnything();
// 调用静态方法
base.doSomething();
}
}
class Base {
public static void doSomething() {
System.out.println("我是父类的静态方法");
}
public void doAnything() {
System.out.println("我是父类的非静态方法");
}
}
class Sub extends Base {
public static void doSomething() {
System.out.println("我是子类的静态方法");
}
@Override
public void doAnything() {
System.out.println("我是子类的非静态方法");
}
}
看到程序中子类的 doAnything() 方法覆写类父类的方法,没有问题,而 doSomething() 呢?它与父类方法名相同、输入输出也相同,按道理也是覆写,事实是怎么样的呢?
结果让人困惑,同样是调用子类方法,一个执行了子类方法,一个执行了父类方法,原因何在?
我们知道实例对象有两个类型:表面类型、实际类型,表面类型是声明时的类型,实际类型是产生时的类型,在这个例子中,base 的表面类型是 Base,实际类型是 Sub。对于非静态方法,它是根据对象的实际类型来执行的;而对于静态方法它就比较特殊,它是通过类名访问,其次是可以通过对象访问,如果是通过对象调用的静态方法,JVM 则会通过对象的表面类型查找静态方法的入口,那么上面程序打印“我是父类的静态方法”也就不足为奇了。
通过这个实例也可以看到,覆写静态方法这一做法建议阅之弃之。