基本类型——读《编写高质量代码:改善Java程序的151个建议》(二)

读书,收获,分享
建议后面的五角星仅代表笔者个人需要注意的程度。
Talk is cheap.Show me the code

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

取余示例代码:

public static void main(String[] args) {
    System.out.println(-1 % 2 == 1 ? "奇数" : "偶数");
    //运行结结果:偶数
    System.out.println(-1 % 2 == 0 ? "偶数" : "奇数");
    //运行结结果:奇数
}

了解一下Java中的取余(%标示符)算法,模拟代码如下:

//模拟java取余计算,dividend被除数,divisor除数
public static int remainder(int dividend, int divisor) {
    return dividend - dividend / divisor * divisor;
}

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

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

示例代码:

public static void main(String[] args) {
    System.out.println(10.00 - 9.60);
    //运行结果:0.40000000000000036
}

为什么不是期望的结果0.4呢?首先看下0.4转二进制的过程

//0.4转二进制
0.4 * 2 = 0.8 ————————————————0
0.8 * 2 = 1.6 ————————————————1
0.6 * 2 = 1.2 ————————————————1
0.2 * 2 = 0.4 ————————————————0
0.4 * 2 = 0.8 ————————————————0
0.8 * 2 = 1.6 ————————————————1
......//无限循环,类似十进制的1/3

所以在计算机中浮点数有可能(注意是可能)是不准确的,它只能无限接近准确值,而不能完全精确。

处理货币的解决方法:

  1. 使用BigDecimal
    BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方案。
  2. 使用整型
    把参与运算的值扩大100倍,并转变为整型,然后在展现时再缩小100倍,这样处理的好处是计算简单、准确,一般在非金融行业(如零售行业)应用较多。

扩展:
小数用二进制如何表示
文字描述:将该数字乘以2,取出整数部分作为二进制表示的第1位;然后再将小数部分乘以2,将得到的整数部分作为二进制表示的第2位;以此类推,直到小数部分为0。
特殊情况:小数部分出现循环,无法停止,则用有限的二进制位无法准确表示一个小数,这也是在编程语言中表示小数会出现误差的原因。
二进制表示的小数如何转换为十进制
文字描述:从左到右,v[i] * 2^( - i ), i 为从左到右的index,v[i]为该位的值。
例子:0.4 = 0 * 2 ^ -1 + 1 * 2 ^ -2 + 1 * 2 ^ -3 + 0 * 2 ^ -4 + ......

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

示例代码:

public static void main(String[] args) {
    long val = 30 * 10000 * 1000 * 60 * 8;
    System.out.println(val);
    //运行结果:-2028888064
    /**
     * Java是先运算然后再进行类型转换的,
     * 三个运算参数都是int类型,三者相乘的结果虽然也是int类型,但是已经超过了int的最大值,
     * 所以其值就是负值了(为什么是负值?因为过界了就会从头开始),再转换成long型,结果还是负值。
     */
    //主动声明式类型转化(注意不是强制类型转换):长整型,乘出来的结果也是长整型
    long val2 = 1L * 30 * 10000 * 1000 * 60 * 8;
    System.out.println(val2);
    //运行结果:144000000000
}

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

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

数字越界使检验条件失效
示例代码:

public static void main(String[] args) {
    //输入参数,int类型的最大值
    int order = 2147483647;
    //内置业务参数
    int cnt = 1000;
    if (order > 0 && order + cnt < 2000) {
        System.out.println("处理成功");
        System.out.println("order:" + order + cnt);
    } else {
        System.out.println("超过限额,处理失败");
    }
    //运行结果:处理成功,order:-2147482649
    /**
     *已经超过了int的最大值, 所以其值就是负值了(为什么是负值?因为过界了就会从头开始)
     */
    
}

在单元测试中,有一项测试叫做边界测试(也有叫做临界测试),如果一个方法接收的是int类型的参数,那以下三个值是必测的:0、正最大、负最小,其中正最大和负最小是边界值,如果这三个值都没有问题,方法才是比较安全可靠的。

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

目前Java支持以下七种舍入方式:

  1. ROUND_UP:
    远离零方向舍入。向远离0的方向舍入,也就是说,向绝对值最大的方向舍入,只要舍弃位非0即进位。
  2. ROUND_DOWN:
    趋向零方向舍入。向0方向靠拢,也就是说,向绝对值最小的方向输入,注意:所有的位都舍弃,不存在进位情况。
  3. ROUND_CEILING:
    向正无穷方向舍入。向正最大方向靠拢,如果是正数,舍入行为类似于ROUND_UP;如果为负数,则舍入行为类似于ROUND_DOWN。注意:Math.round方法使用的即为此模式。
  4. ROUND_FLOOR:
    向负无穷方向舍入。向负无穷方向靠拢,如果是正数,则舍入行为类似于ROUND_DOWN;如果是负数,则舍入行为类似于ROUND_UP。
  5. HALF_UP:
    最近数字舍入(5进)。这就是我们最最经典的四舍五入模式。
  6. HALF_DOWN:
    最近数字舍入(5舍)。在四舍五入中,5是进位的,而在HALF_DOWN中却是舍弃不进位。
  7. HALF_EVEN(银行家算法):
    舍去位的数值小于5时,直接舍去;
    舍去位的数值大于等于6时,进位后舍去;
    当舍去位的数值等于5时,分两种情况:5后面还有其他数字(非0),则进位后舍去;若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。

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

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

Java中的基本数据类型没有方法和属性,而包装类就是为了让这些拥有方法和属性,实现对象化交互。如想把一个整型放到List中,就必须使用Integer包装类型。
包装对象和拆箱对象可以自由转换,但是要剔除null值,null值并不能转化为基本类型。

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

建议27:谨慎包装类型的大小比较★★☆☆☆

示例代码:

public static void main(String[] args) {
    Integer i = new Integer(100);
    Integer j = new Integer(90);
    System.out.println(i > j);
    //运行结果:true
    /**
     * 在Java中,“>”和“<”用来判断两个数字类型的大小关系,注意只能是数字型的判断,
     * 对于Integer包装类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的
     * (其他包装类型是根据相应的value值来比较的,如doubleValue、floatValue等)
     */
    /**
     * 但是两个对象之间的比较就应该采用相应的方法,而不是通过Java的默认机制来处理
     */
    System.out.println(i.compareTo(j));
    //运行结果:1
}

注意:应该养成良好的编码习惯

建议28:优先使用整型池★★☆☆☆

示例代码:

public static void main(String[] args) {
    int i = 10;
    int j = 10;
    Integer ii = new Integer(i);
    Integer jj = new Integer(j);
    //注意下面的"=="是引用的比较
    System.out.println("new产生的Integer对象" + (ii == jj));
    ii = i;
    jj = j;
    System.out.println("自动装箱的Integer对象" + (ii == jj));
    ii = Integer.valueOf(i);
    jj = Integer.valueOf(j);
    System.out.println("valueOf产生的Integer对象" + (ii == jj));
    //运行结果:
    //new产生的Integer对象false
    //自动装箱的Integer对象true
    //valueOf产生的Integer对象true
}

解释:

  1. new产生的Integer对象
    new声明的就是要生成一个新的对象,地址肯定不等
  2. 装箱生成的对象
    装箱动作是通过valueOf方法实现的,所以后面两个算法是相同的
  3. Integer.valueOf的实现代码
//Integer类中的内部缓存类
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}
//Integer类中的valueOf方法
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

IntegerCache.cache是IntegerCache内部类的一个静态数组,容纳的是-128到127之间的Integer对象。通过valueOf产生包装对象时,如果int参数在-128和127之间,则直接从整型池中获得对象,不在该范围的int类型则通过new生成包装对象。

所以在判断对象是否相等的时候,最好是用equals方法,避免用“==”产生非预期结果。

注意:通过包装类的valueOf生成包装实例可以显著提高空间和时间性能。

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

无论从安全性、性能还是稳定性方面,基本类型都是首选方案。

例如int可以加宽转变为long型,Integer却不能转为Long

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

示例代码:

public static void main(String[] args) {
    //Random的有参构造
    Random r = new Random(1000);
    for (int i = 1; i <= 4; i++) {
        System.out.println("第" + i + "次:" + r.nextInt());
    }
    /**
     * 程序第一次的运行结果
     * 第1次:-1244746321
     * 第2次:1060493871
     * 第3次:-1826063944
     * 第4次:1976922248
     */
    /**
     * 程序第二次的运行结果
     * 第1次:-1244746321
     * 第2次:1060493871
     * 第3次:-1826063944
     * 第4次:1976922248
     */
     //在同一台机器上,甭管运行多少次,所打印的随机数都是相同的
}

问题何在?
因为产生随机数的种子被固定了,在Java中,随机数的产生取决于种子,随机数和种子之间的关系遵从以下两个规则:

  1. 种子不同,产生不同的随机数。
  2. 种子相同,即使实例不同也产生相同的随机数。

Random类的默认种子(无参构造)是System.nanoTime()的返回值,注意这个值是距离某一个固定时间点的纳秒数,不同的操作系统和硬件有不同的固定时间点,也就是说不同的操作系统其纳秒值是不同的,而同一个操作系统纳秒值也会不同,随机数自然也就不同了。(System.nanoTime不能用于计算日期,那是因为“固定”的时间点是不确定的,纳秒值甚至可能是负值,这点与System. currentTimeMillis不同。)

注意:若非必要,不要设置随机数种子。
在Java中有两种方法可以获得不同的随机数:通过java.util.Random类获得随机数的原理和Math.random方法相同,Math.random()方法也是通过生成一个Random类的实例,然后委托nextDouble()方法的,两者是殊途同归,没有差别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容