改善 Java 程序的 N 个建议(二)

Updated on in Java with 611 views

建议22:用整型类型处理货币

这一点在很早之前也想过,当时看到很多电商的接口返回的货币都是以“分”为单位,为就想以“元”为单位不是更合理,更加符合人的思维逻辑吗?那就看下下面的案例。

小明去超市用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 倍,在零售行业一般应用较多,运算简单。

建议24:边界,边界,还是边界

最近华为出了一款非常火爆的折叠屏手机 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("超过限额,预定失败");
			}
		}
	}
}

逻辑看似很简单,不一一解读了,尝试了几个测试结果如下:
image.png

前两个测试结果很正常,一个是没有超限,另一个是超限的,但是最后这个结果有点奇怪,照理来说肯定超限,2147483647 这个数字眼熟吗?没错,是 Java 中 int 的最大取值,那当前 order 为该值,再加上 1000,结果为 -2147482649,这个结果肯定小于正数 2000 了,这类问题就是数字越界使校验条件失效,在 WEB 开发中无论前端还是后端务必校验用户提交的数据。

建议33:不要覆写静态方法

看到这个建议,可能会有疑惑,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() 呢?它与父类方法名相同、输入输出也相同,按道理也是覆写,事实是怎么样的呢?

image.png

结果让人困惑,同样是调用子类方法,一个执行了子类方法,一个执行了父类方法,原因何在?

我们知道实例对象有两个类型:表面类型、实际类型,表面类型是声明时的类型,实际类型是产生时的类型,在这个例子中,base 的表面类型是 Base,实际类型是 Sub。对于非静态方法,它是根据对象的实际类型来执行的;而对于静态方法它就比较特殊,它是通过类名访问,其次是可以通过对象访问,如果是通过对象调用的静态方法,JVM 则会通过对象的表面类型查找静态方法的入口,那么上面程序打印“我是父类的静态方法”也就不足为奇了。

通过这个实例也可以看到,覆写静态方法这一做法建议阅之弃之。


标题:改善 Java 程序的 N 个建议(二)
作者:Jeffrey

Responses
取消