【编写高质量代码:改善Java程序的151个建议】第2章:基本类型___建议21~30

不积跬步,无以至千里;不积小流,无以成江海。 --荀子《劝学篇》

建议21:用偶判断,不用奇判断

判断一个数是奇数还是偶数是小学里的基本知识,能够被2整除的整数是偶数,不能被2整除的数是奇数,这规则简单明了,还有什么可考虑的?好,我们来看一个例子,代码如下:

import java.util.Scanner;

public class Client21 {
    public static void main(String[] args) {
        // 接收键盘输入参数
        Scanner input = new Scanner(System.in);
        System.out.println("输入多个数字判断奇偶:");
        while (input.hasNextInt()) {
            int i = input.nextInt();
            String str = i + "-->" + (i % 2 == 1 ? "奇数" : "偶数");
            System.out.println(str);

        }
    }
}

输入多个数字,然后判断每个数字的奇偶性,不能被2整除的就是奇数,其它的都是偶数,完全是根据奇偶数的定义编写的程序,我们开看看打印的结果:

  输入多个数字判断奇偶:1  2  0  -1 -2     1-->奇数    2-->偶数    0-->偶数     -1-->偶数       -2-->偶数

前三个还很靠谱,第四个参数-1怎么可能是偶数呢,这Java也太差劲了吧。

看到这段程序,大家都会心的笑了,原来Java这么处理取余计算的呀,根据上面的模拟取余可知,当输入-1的时候,运算结果为-1,当然不等于1了,所以它就被判定为偶数了,也就是我们的判断失误了。问题明白了,修正也很简单,改为判断是否是偶数即可。代码如下:     i % 2 == 0 ? "偶数" : "奇数";

注意:对于基础知识,我们应该"知其然,并知其所以然"。

建议22:用证书类型处理货币

在日常生活中,最容易接触的小数就是货币,比如,你付给售货员10元钱购买一个9.6元的零食,售货员应该找你0.4元,也就是4毛钱才对,我们来看下面的程序:

public class Client22 {
    public static void main(String[] args) {
        System.out.println(10.00-9.60);
    }
}

我们的期望结果是0.4,也应该是这个数字,但是打印出来的却是:0.40000000000000036,这是为什么呢?

这是因为在计算机中浮点数有可能(注意是有可能)是不准确的,它只能无限接近准确值,而不能完全精确。为什么会如此呢?这是由浮点数的存储规则所决定的,我们先来看看0.4这个十进制小数如何转换成二进制小数,使用"乘2取整,顺序排列"法,我们发现0.4不能使用二进制准确的表示,在二进制数世界里它是一个无限循环的小数,就像在十进制的世界里没有办法唯一准确表示1/3。

public class Client22 {
    public static void main(String[] args) {
        NumberFormat f = new DecimalFormat("#.##");
        System.out.println(f.format(10.00-9.60));
    }
}

打印出的结果是0.4,看似解决了。但是隐藏了一个很深的问题。我们来思考一下金融行业的计算方法,会计系统一般记录小数点后的4为小数,但是在汇总、展现、报表中、则只记录小数点后的2位小数,如果使用浮点数来计算货币,想想看,在大批量加减乘除后结果会有很大的差距(其中还涉及到四舍五入的问题)!会计系统要求的就是准确,但是因为计算机的缘故不准确了,那真是罪过,要解决此问题有两种方法:

1、使用BigDecimal

BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方案。

代码实例

852390d1969b5be97da83fb0f179881eb80.jpg

声明BigDecimal对象的时候一定要使用它构造参数为String的类型的构造器。

2、使用整型

把参与运算的值扩大100倍,并转为整型,然后在展现时再缩小100倍,这样处理的好处是计算简单,准确,一般在非金融行业(如零售行业)应用较多。此方法还会用于某些零售POS机,他们输入和输出的全部是整数,那运算就更简单了.

注:乘2取整,顺序排列

十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位。或者达到所要求的精度为止。
 

0.96875
X 2
1.9375 取1后变成 0.9375 (以下省略不写了)
X 2
1.875 取1
X 2
1.75 取1
X 2
1.5 取1
X 2
1.0 取1后变成 0,结束转换

0.96875=(0.11111)2

建议23:不要让类型默默转换

我们做一个小学生的题目,光速每秒30万公里,根据光线的旅行时间,计算月球和地球,太阳和地球之间的距离。代码如下: 

public class Client23 {
    // 光速是30万公里/秒,常量
    public static final int LIGHT_SPEED = 30 * 10000 * 1000;

    public static void main(String[] args) {
        System.out.println("题目1:月球照射到地球需要一秒,计算月亮和地球的距离。");
        long dis1 = LIGHT_SPEED * 1;
        System.out.println("月球与地球的距离是:" + dis1 + " 米 ");
        System.out.println("-------------------------------");
        System.out.println("题目2:太阳光照射到地球需要8分钟,计算太阳到地球的距离.");
        // 可能要超出整数范围,使用long型
        long dis2 = LIGHT_SPEED * 60 * 8;
        System.out.println("太阳与地球之间的距离是:" + dis2 + " 米");
    }
}

a433bd7a559defea6999081df1b35138ef9.jpg

 

Java是先运算然后再进行类型转换的,具体的说dis2的三个运算参数都是int型,三者相乘结果也是int型,但是超出了int的最大值,就变成负值了,越界了重新开始。

而dis3中int60变为了long60L,长整型,(Java基本转换规则,向数据范围大的方向转换,也叫加宽类型),在还没有超出int型范围的时候就已经转化成long型了,解决了越界问题,实际开发中,更通用的做法是主动声明类型转化,而不是强制类型转换,

long dis2 = 1L * LIGHT_SPEED * 60L * 8

既然期望的结果是long型,那就让第一个参与的参数也是Long(1L)吧,也就说明"嗨"我已经是长整型了,你们都跟着我一块转为长整型吧。

注意:基本类型转换时,使用主动声明方式减少不必要的Bug。

建议24:边界还是边界

某商家生产的电子产品非常畅销,需要提前30天预订才能抢到手,同时还规定了一个会员可拥有的最多产品数量,目的是为了防止囤积压货肆意加价。会员的预订过程是这样的:先登录官方网站,选择产品型号,然后设置需要预订的数量,提交,符合规则即提示下单成功,不符合规则提示下单失败,后台的处理模拟如下:

    public class DynamicCompileDemo {
        // 一个会员拥有产品的最多数量
        public final static int LIMIT = 2000;
        public static void main(String[] args) {
            // 会员当前用有的产品数量
            int cur = 1000;
            Scanner input = new Scanner(System.in);
            System.out.println("请输入需要预定的数量:");
            while (input.hasNextInt()) {
                int order = input.nextInt();
                if (order > 0 && order + cur <= LIMIT) {
                    cur = cur + order;
                    System.out.println("你已经成功预定:" + cur  + " 个产品");
                } else {
                    System.out.println("超过限额,预定失败!");
                }
            }
        }
    }

dab5c9ab1d141b12f49e9a1c1514c604d7e.jpg

竟然预定了-2147481829 个产品,真的是奇葩啊。

看着2147483647这个数字很眼熟?那就对了,这个数字就是int类型的最大值,没错,有人输入了一个最大值,使校验条件失败了,Why?我们来看程序,order的值是2147483647那再加上1000就超出int的范围了,其结果是-2147482649,那当然是小于正数2000了!一句归其原因:数字越界使校验条件失效。

在单元测试中,有一项测试叫做边界测试(也叫临界测试),如果一个方法接收的是int类型的参数,那么以下三个值是必须测试的:0、正最大、负最小,其中正最大、负最小是边界值,如果这三个值都没有问题,方法才是比较安全可靠的。我们的例子就是因为缺少边界测试,致使生产系统产生了严重的偏差。

建议25:不要让四舍五入亏了一方

System.out.println("10.5近似值: "+Math.round(10.5));
System.out.println("-10.5近似值: "+Math.round(-10.5));

输出结果为:10.5近似值: 11       -10.5近似值: -10

银行家舍入(Banker's Round)的近似算法:

四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。

我们举例说明,取2位精度;

round(10.5551)  =  10.56   round(10.555)  =  10.56   round(10.545)  =  10.54 

注意:根据不同的场景,慎重选择不同的舍入模式,以提高项目的精准度,减少算法损失。

建议26:提防包装类型的null值

包装类型参与运算时,要做null值校验。

建议27:谨慎包装类型额大小比较

建议28:优先使用整形池

1、valueOf()返回的是原对象

2、valueOf()方法返回的是现在到1970年1月1日00:00:00的数值类型的毫秒数

3、包装对象的valueOf()方法返回该包装对象对应的原始值

4、装箱动作是通过valueOf方法实现的

通过valueOf产生包装对象时,如果int参数在-128到127之间,直接从整型池中获取对象,不在范围内的int类型则通过new生成包装类型。

127的包装对象直接从整型池中获得,而128、555超出了整型池范围,是通过new产生的一个新对象,地址不同,==对比就不同了。

整型池的存在提高了系统性能,节约了内存空间,也就是在生成包装对象的时候使用valueOf生成,而不是通过构造函数来生成的原因。

顺便说一下,在判断对象释放相等的时候,最好使用equals方法,避免使用==产生非预期的效果。

这个的实际意思是不是就是:

使用Integer.valueOf(1)比使用Integer i = new Interger(1);

系统性能好,更节约内存空间。

建议29:优先选择基本类型

建议30:不要随便设置随机种子

1、什么是随机种子?

随机种子是基于随机数的计算机专业术语,随机数是以随机种子作为初始条件,然后通过一定的算法不停迭代产生的。

在Java中,随机数的产生取决于种子,随机数与种子之间的关系:

① 种子不同,产生不同的随机数

② 种子相同,即使实例不同也会产生相同的随机数。

2、设置随机种子的两种方法:

若设置随机种子则相同的种子,产生的随机数相同。若不设置则每次随机的不同。

Random rnd = new Random();

rnd.setSeed()用于设置种子。

rnd.nextInt() 用于产生随机数。

rnd.nextInt(10); // 0-10之间。
Random r = new Random(1000);//1000就是一个随机种子
for(int i=1; i<=4; i++){
    System.out.println("第"+i+"次:"+r.nextInt());   
}

3、Java中获取随机数的两种方式

java.util.Random类获得随机数和Math.random()。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值