欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

二、编写高质量的代码—数据类型(笔记)

程序员文章站 2022-06-16 16:17:47
...

 

本博文为《编写高质量代码—改善Java程序的151个建议》一书的阅读笔记。该书从很多方面给予了编写高质量代码的宝贵经验。而且该书应该是那种开发经验越丰富,体会越深的书籍。在阅读过程中,从该书中收获良多,这里主要作下书籍笔记,有体会的地方加点自己的想法。受限于知识水平,部分内容还没能深刻体会,所以更多更好的内容和具体实例还需要从书中去找寻。

 

 

一、不要用浮点数处理货币

货币在单位是元的时候常常都是带小数的,比如角分。这时候货币的数值就是小数,有些系统为了定义方便,直接定义成float或者double类型,然后运算运算就发现数据不对劲了,小数位总好像差点。我前两个月接手了一个老系统就是这样,非常地坑。 
浮点数由于是二进制存储,对于部分小数根本无法准确表示,只能是近似存储,因此在运算过程中可能出现“意外”的结果。所以对于小数的定义,尤其是涉及到货币相关的处理,数据类型最好定义成BigDecimal,这是专门为了弥补浮点数无法精确计算而设计的类。另外还有种思路是先将小数放大成整数,再作运算。 
以个人的经验,认为定义成BigDecimal最佳,放大成整数还要放大缩小,多一些多余的操作,字段的含义也发生了变化。

二、不要让类型默默转化

在数据类型默认转化时可能出现意想不到的Bug,例如书中所举的例子:long dis = 30 * 10000 * 1000 * 60 * 8;结果dis的值是个负数(-2028888064),因为dis右边运算的结果是Int类型,但是超过最大长度了,所以变成了负值(越界从头算起)。
这种int类型默默转换成long类型可能就会出现问题,所以尽量避免默认转换,比如上面的例子可以在30后面加L,让计算结果为long类型。

三、边界,还界,还是边界

在代码编写的过程中一定要考虑数据类型的边界,不然可能出现意想不到的问题。比如书中所举的例子int order = 用户输入的订单数,if( order >0 && order+1000<=2000 )就预定成功,这段逻辑初看起来没有任何问题,可是如果order=2147483674竟然可以预定成功。根据原因就是因为order=2147483674时再加1000就已经超过Int最大值,得到负数肯定比2000小。
所以在编写程序一定要注意边界值,另外单元测试时也要做边界测试,假如输入参数是int类型,就要考虑正最大、负最小、0这些边界值。

四、不要让四舍五入亏了一方

四舍五入是一种常见的计算方法,通常的做法都是小数位小于等于4则舍弃,大于等于5则进位后舍弃。但是这种算法对于银行的利息计算就会出现亏损,书中举了个非常好的例子,结论是考虑银行存款足够多,数字均匀发布的情况下每10笔利息银行就会亏损0.005元。
所以在类似银行利息计算的时候,需要使用修正的四舍五入算法,即银行家舍入近似算法,具体规则为:
1. 舍入位小于5直接舍弃;
2. 舍入位数值大于等于6则进位后舍弃;
3. 舍入位等于5时,若5后面还有其它非0数字,则进位后舍弃,若5后面是0(或最后一个数字),则根据5前一位的奇偶性判断是否需要进位,奇数则进位,偶数则舍弃。 
简言之,就是四舍六入五考虑,五后非零则进一,五后为零看奇偶,五前为偶应舍弃,五后为奇要进一。
使用银行家的舍入算法,可以通过设置舍入模式为RoundingMode.HALF_EVEN实现。在普通项目中舍入模式不会有太大影响,可以直接使用Math.round方法,但是在大量与货币数字交互的项目中,则需要根据场景选择合适的舍入算法了(RoundingMode有多种舍入模式)。

五、提防包装类型的null值

包装类型在参与运算时要做null值校验,不然若包装类型为空,取值运算时会抛出空指针异常。因此为了程序的健壮性考虑,运算时需做校验。

六、谨慎包装类型的大小比较

包装类型不要直接通过比较运算符(>,<,=)进行比较,因为包装类型是对象应该通过compareTo就去进行比较。

七、优先使用整型池

通过包装类的valueOf生成包装实例可以显著提高空间和时间性能,以Integer为例,Integer会有一个内部的缓存,缓存-128到127之间的对象,而使用valueOf方法生成实例时,如果在范围内就直接返回缓存的对象了,这样就提高了性能。
另外包装类型进行相等判断时最好使用equals,而非==。

八、优先选择基本类型

无论从安全性、性能、稳定性考虑,基本类型相比于其对应的包装类型都是应该优先考虑选用的类型。

九、不要随便设置随机种子 

在Java项目中,通常使用Math.random方法和Random类来生成随机数,其中Random类可以设置随机数种子,而一旦设置随机数种子,则在同一台机器 上不论运行多少次随机数总是相同的。因为随机数与种子有如下规则:
1. 种子不同,随机数不同;
2. 种子相同,即使实例不同,也产生相同的随机数。
而Random的默认构造类,种子是seedUniquifier() ^ System.nanoTime(),这样保证了种子的不同。所以想生成“真正的”随机数,不要随便设置随机种子。

 

十、正确使用String、StringBuffer、StringBuilder

三种字符序列的不同使用场景:
1. String类:由于String对象是不可变的,所以String类应该在字符串不经常变化的场景中使用,例如常量的声明、少量变量运算等。

2. StringBuffer类:在频繁进行字符串运算(如拼接、替换、删除等),而且运行在多线程环境中时,应该考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。

3. StringBuilder类:在频繁进行字符串运算(如拼接、替换、删除等),而且运行在单线程环境中时,应该考虑使用StringBuilder,如SQL语句的拼装、JSON封装。