阅读目录

  • 建议26:提防包装类型的null值
  • 建议27:谨慎包装类型的大小比较
  • 建议28:优先使用整型池
  • 建议29:优先选择基本类型
  • 建议30:不要随便设置随机种子
回到顶部

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

  我们知道Java引入包装类型(Wrapper Types)是为了解决基本类型的实例化问题,以便让一个基本类型也能参与到面向对象的编程世界中。而在Java5中泛型更是对基本类型说了"不",如果把一个整型放入List中,就必须使用Integer包装类型。我们看一段代码:

 1 import java.util.ArrayList;2 import java.util.List;3 4 public class Client26 {5 6     public static int testMethod(List<Integer> list) {7         int count = 0;8         for (int i : list) {9             count += i;
10         }
11         return count;
12     }
13
14     public static void main(String[] args) {
15         List<Integer> list = new ArrayList<Integer>();
16         list.add(1);
17         list.add(2);
18         list.add(null);
19         System.out.println(testMethod(list));
20     }
21 }

  testMethod接收一个元素是整型的List参数,计算所有元素之和,这在统计和项目中很常见,然后编写一个测试testMethod,在main方法中把1、2和空值都放到List中,然后调用方法计算,现在思考一下会不会报错。应该不会吧,基本类型和包装类型都是可以通过自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)自由转换的,null应该可以转换为0吧,真的是这样吗?运行之后的结果是:  Exception in thread "main" java.lang.NullPointerException  运行失败,报空指针异常,我们稍稍思考一下很快就知道原因了:在程序for循环中,隐含了一个拆箱过程,在此过程中包装类型转换为了基本类型。我们知道拆箱过程是通过调用包装对象的intValue方法来实现的,由于包装类型为null,访问其intValue方法报空指针异常就在所难免了。问题清楚了,修改也很简单,加入null值检查即可,代码如下:  
public static int testMethod(List<Integer> list) {int count = 0;for (Integer i : list) {count += (i != null) ? i : 0;}return count;}

  上面以Integer和int为例说明了拆箱问题,其它7个包装对象的拆箱过程也存在着同样的问题。包装对象和拆箱对象可以自由转换,这不假,但是要剔除null值,null值并不能转换为基本类型。对于此问题,我们谨记一点:包装类型参与运算时,要做null值校验。

回到顶部

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

  基本类型是可以比较大小的,其所对应的包装类型都实现了Comparable接口,也说明了此问题,那我们来比较一下两个包装类型的大小,代码如下:

 1 public class Client27 {2     public static void main(String[] args) {3         Integer i = new Integer(100);4         Integer j = new Integer(100);5         compare(i, j);6     }7 8     public static void compare(Integer i, Integer j) {9         System.out.println(i == j);
10         System.out.println(i > j);
11         System.out.println(i < j);
12
13     }
14 }

  代码很简单,产生了两个Integer对象,然后比较两个的大小关系,既然包装类型和基本类型是可以自由转换的,那上面的代码是不是就可以打印出两个相等的值呢?让事实说话,运行结果如下:

  false  false  false

  竟然是3个false,也就是说两个值之间不相等,也没大小关系,这个也太奇怪了吧。不奇怪,我们来一 一解释:

  1. i==j:在java中"=="是用来判断两个操作数是否有相等关系的,如果是基本类型则判断值是否相等,如果是对象则判断是否是一个对象的两个引用,也就是地址是否相等,这里很明显是两个对象,两个地址不可能相等。
  2. i>j 和 i<j:在Java中,">" 和 "<" 用来判断两个数字类型的大小关系,注意只能是数字类型的判断,对于Integer包装类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的(其它包装类型是根据相应的value值比较的,如doubleValue,floatValue等),那很显然,两者不肯能有大小关系的。

问题清楚了,修改总是比较容易的,直接使用Integer的实例compareTo方法即可,但是这类问题的产生更应该说是习惯问题,只要是两个对象之间的比较就应该采用相应的方法,而不是通过Java的默认机制来处理,除非你确定对此非常了解。

回到顶部

建议28:优先使用整型池

  上一个建议我们解释了包装对象的比较问题,本建议将继续深入讨论相关问题,首先看看如下代码: 

 1 import java.util.Scanner;2 3 public class Client28 {4     public static void main(String[] args) {5         Scanner input = new Scanner(System.in);6         while (input.hasNextInt()) {7             int tempInt = input.nextInt();8             System.out.println("\n=====" + tempInt + " 的相等判断=====");9             // 两个通过new产生的对象
10             Integer i = new Integer(tempInt);
11             Integer j = new Integer(tempInt);
12             System.out.println(" new 产生的对象:" + (i == j));
13             // 基本类型转换为包装类型后比较
14             i = tempInt;
15             j = tempInt;
16             System.out.println(" 基本类型转换的对象:" + (i == j));
17             // 通过静态方法生成一个实例
18             i = Integer.valueOf(tempInt);
19             j = Integer.valueOf(tempInt);
20             System.out.println(" valueOf产生的对象:" + (i == j));
21         }
22     }
23 }

输入多个数字,然后按照3中不同的方式产生Integer对象,判断其是否相等,注意这里使用了"==",这说明判断的不是同一个对象。我们输入三个数字127、128、555,结果如下:

  127
=====127 的相等判断=====
 new 产生的对象:false
 基本类型转换的对象:true
 valueOf产生的对象:true
128
=====128 的相等判断=====
 new 产生的对象:false
 基本类型转换的对象:false
 valueOf产生的对象:false
555
=====555 的相等判断=====
 new 产生的对象:false
 基本类型转换的对象:false
 valueOf产生的对象:false

很不可思议呀,数字127的比较结果竟然和其它两个数字不同,它的装箱动作所产生的对象竟然是同一个对象,valueOf产生的也是同一个对象,但是大于127的数字和128和555的比较过程中产生的却不是同一个对象,这是为什么?我们来一个一个解释。

(1)、new产生的Integer对象

    new声明的就是要生成一个新的对象,没二话,这是两个对象,地址肯定不等,比较结果为false。

(2)、装箱生成的对象

  对于这一点,首先要说明的是装箱动作是通过valueOf方法实现的,也就是说后两个算法相同的,那结果肯定也是一样的,现在问题是:valueOf是如何生成对象的呢?我们来阅读以下Integer.valueOf的源码: 

 1  /**2      * Returns an {@code Integer} instance representing the specified3      * {@code int} value.  If a new {@code Integer} instance is not4      * required, this method should generally be used in preference to5      * the constructor {@link #Integer(int)}, as this method is likely6      * to yield significantly better space and time performance by7      * caching frequently requested values.8      *9      * This method will always cache values in the range -128 to 127,
10      * inclusive, and may cache other values outside of this range.
11      *
12      * @param  i an {@code int} value.
13      * @return an {@code Integer} instance representing {@code i}.
14      * @since  1.5
15      */
16     public static Integer valueOf(int i) {
17         assert IntegerCache.high >= 127;
18         if (i >= IntegerCache.low && i <= IntegerCache.high)
19             return IntegerCache.cache[i + (-IntegerCache.low)];
20         return new Integer(i);
21     }

这段代码的意思已经很明了了,如果是-128到127之间的int类型转换为Integer对象,则直接从cache数组中获得,那cache数组里是什么东西,JDK7的源代码如下:

 1  /**2      * Cache to support the object identity semantics of autoboxing for values between3      * -128 and 127 (inclusive) as required by JLS.4      *5      * The cache is initialized on first usage.  The size of the cache6      * may be controlled by the -XX:AutoBoxCacheMax=<size> option.7      * During VM initialization, java.lang.Integer.IntegerCache.high property8      * may be set and saved in the private system properties in the9      * sun.misc.VM class.
10      */
11
12     private static class IntegerCache {
13         static final int low = -128;
14         static final int high;
15         static final Integer cache[];
16
17         static {
18             // high value may be configured by property
19             int h = 127;
20             String integerCacheHighPropValue =
21                 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
22             if (integerCacheHighPropValue != null) {
23                 int i = parseInt(integerCacheHighPropValue);
24                 i = Math.max(i, 127);
25                 // Maximum array size is Integer.MAX_VALUE
26                 h = Math.min(i, Integer.MAX_VALUE - (-low));
27             }
28             high = h;
29
30             cache = new Integer[(high - low) + 1];
31             int j = low;
32             for(int k = 0; k < cache.length; k++)
33                 cache[k] = new Integer(j++);
34         }
35
36         private IntegerCache() {}
37     }

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

  明白了这一点,要理解上面的输出结果就迎刃而解了,127的包装对象是直接从整型池中获得的,不管你输入多少次127这个数字,获得的对象都是同一个,那地址自然是相等的。而128、555超出了整型池范围,是通过new产生一个新的对象,地址不同,当然也就不相等了。

  以上的理解也是整型池的原理,整型池的存在不仅仅提高了系统性能,同时也节约了内存空间,这也是我们使用整型池的原因,也就是在声明包装对象的时候使用valueOf生成,而不是通过构造函数来生成的原因。顺便提醒大家,在判断对象是否相等的时候,最好使用equals方法,避免使用"=="产生非预期效果。

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

回到顶部

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

  包装类型是一个类,它提供了诸如构造方法,类型转换,比较等非常实用的功能,而且在Java5之后又实现了与基本类型的转换,这使包装类型如虎添翼,更是应用广泛了,在开发中包装类型已经随处可见,但无论是从安全性、性能方面来说,还是从稳定性方面来说,基本类型都是首选方案。我们看一段代码:

 1 public class Client29 {2     public static void main(String[] args) {3         Client29 c = new Client29();4         int i = 140;5         // 分别传递int类型和Integer类型6         c.testMethod(i);7         c.testMethod(new Integer(i));8     }9
10     public void testMethod(long a) {
11         System.out.println(" 基本类型的方法被调用");
12     }
13
14     public void testMethod(Long a) {
15         System.out.println(" 包装类型的方法被调用");
16     }
17 }

  在上面的程序中首先声明了一个int变量i,然后加宽转变成long型,再调用testMethod()方法,分别传递int和long的基本类型和包装类型,诸位想想该程序是否能够编译?如果能编译,输出结果又是什么呢?

  首先,这段程序绝对是能够编译的。不过,说不能编译的同学还是动了一番脑筋的,你可能猜测以下这些地方不能编译:

  (1)、testMethod方法重载问题。定义的两个testMethod()方法实现了重载,一个形参是基本类型,一个形参是包装类型,这类重载很正常。虽然基本类型和包装类型有自动装箱、自动拆箱功能,但并不影响它们的重载,自动拆箱(装箱)只有在赋值时才会发生,和编译重载没有关系。

  (2)、c.testMethod(i) 报错。i 是int类型,传递到testMethod(long a)是没有任何问题的,编译器会自动把 i 的类型加宽,并将其转变为long型,这是基本类型的转换法则,也没有任何问题。

  (3)、c.testMethod(new Integer(i))报错。代码中没有testMethod(Integer i)方法,不可能接收一个Integer类型的参数,而且Integer和Long两个包装类型是兄弟关系,不是继承关系,那就是说肯定编译失败了?不,编译时成功的,稍后再解释为什么这里编译成功。

既然编译通过了,我们看一下输出:

    基本类型的方法被调用
       基本类型的方法被调用

  c.testMethod(i)的输出是正常的,我们已经解释过了,那第二个输出就让人困惑了,为什么会调用testMethod(long a)方法呢?这是因为自动装箱有一个重要原则:基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转变成宽类型的包装类型。这句话比较拗口,简单的说就是,int可以加宽转变成long,然后再转变成Long对象,但不能直接转变成包装类型,注意这里指的都是自动转换,不是通过构造函数生成,为了解释这个原则,我们再来看一个例子:

 1 public class Client29 {2     public static void main(String[] args) {3         Client29 c = new Client29();4         int i = 140;5         c.testMethod(i);6     }7 8     public void testMethod(Long a) {9         System.out.println(" 包装类型的方法被调用");
10     }
11 }

这段程序的编译是不通过的,因为i是一个int类型,不能自动转变为Long型,但是修改成以下代码就可以通过了:

int i = 140; long a =(long)i; c.testMethod(a);
这就是int先加宽转变成为long型,然后自动转换成Long型,规则说明了,我们继续来看testMethod(Integer.valueOf(i))是如何调用的,Integer.valueOf(i)返回的是一个Integer对象,这没错,但是Integer和int是可以互相转换的。没有testMethod(Integer i)方法?没关系,编译器会尝试转换成int类型的实参调用,Ok,这次成功了,与testMethod(i)相同了,于是乎被加宽转变成long型---结果也很明显了。整个testMethod(Integer.valueOf(i))的执行过程是这样的:

  (1)、i 通过valueOf方法包装成一个Integer对象

  (2)、由于没有testMethod(Integer i)方法,编译器会"聪明"的把Integer对象转换成int。

  (3)、int自动拓宽为long,编译结束

  使用包装类型确实有方便的方法,但是也引起一些不必要的困惑,比如我们这个例子,如果testMethod()的两个重载方法使用的是基本类型,而且实参也是基本类型,就不会产生以上问题,而且程序的可读性更强。自动装箱(拆箱)虽然很方便,但引起的问题也非常严重,我们甚至都不知道执行的是哪个方法。

  注意:重申,基本类型优先考虑。

回到顶部

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

  随机数用的地方比较多,比如加密,混淆计算,我们使用随机数期望获得一个唯一的、不可仿造的数字,以避免产生相同的业务数据造成混乱。在Java项目中通常是通过Math.random方法和Random类来获得随机数的,我们来看一段代码:

 1 import java.util.Random;2 3 public class Client30 {4     public static void main(String[] args) {5         Random r = new Random();6         for(int i=1; i<=4; i++){7             System.out.println("第"+i+"次:"+r.nextInt());8             9         }
10     }
11 }

代码很简单,我们一般都是这样获得随机数的,运行此程序可知,三次打印,的随机数都不相同,即使多次运行结果也不同,这也正是我们想要随机数的原因,我们再来看看下面的程序:

1 public class Client30 {
2     public static void main(String[] args) {
3         Random r = new Random(1000);
4         for(int i=1; i<=4; i++){
5             System.out.println("第"+i+"次:"+r.nextInt());
6
7         }
8     }
9 }

上面使用了Random的有参构造,运行结果如下:

第1次:-1244746321
第2次:1060493871
第3次:-1826063944
第4次:1976922248  

  计算机不同输出的随机数也不同,但是有一点是相同的:在同一台机器上,甭管运行多少次,所打印的随机数都是相同的,也就是说第一次运行,会打印出这几个随机数,第二次运行还是打印出这三个随机数,只要是在同一台机器上,就永远都会打印出相同的随机数,似乎随机数不随机了,问题何在?

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

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

  看完上面两个规则,我们再来看这个例子,会发现问题就出在有参构造上,Random类的默认种子(无参构造)是System.nonoTime()的返回值(JDK1.5版本以前默认种子是System.currentTimeMillis()的返回值),注意这个值是距离某一个固定时间点的纳秒数,不同的操作系统和硬件有不同的固定时间点,也就是说不同的操作系统其纳秒值是不同的,而同一个操作系统纳秒值也会不同,随机数自然也就不同了.(顺便说下,System.nonoTime不能用于计算日期,那是因为"固定"的时间是不确定的,纳秒值甚至可能是负值,这点与System.currentTiemMillis不同)。

  new Random(1000)显示的设置了随机种子为1000,运行多次,虽然实例不同,但都会获得相同的四个随机数,所以,除非必要,否则不要设置随机种子。

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

注意:若非必要,不要设置随机数种子。

作者:阿赫瓦里
出处:http://www.cnblogs.com/selene/
本文以学习、研究和分享为主,版权归作者和博客园共有,欢迎转载,如果文中有不妥或者错误的地方还望大神您不吝指出。如果觉得本文对您有所帮助不如【推荐】一下吧!如果你有更好的建议,不如留言一起讨论,共同进步!此外,大家也可以支持一下自家苹果, 再次感谢您耐心的读完本篇文章。

转载于:https://www.cnblogs.com/LH923613603/p/6926254.html

转载---编写高质量代码:改善Java程序的151个建议(第2章:基本类型___建议26~30)相关推荐

  1. 转载----编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)...

    阅读目录 建议1:不要在常量和变量中出现易混淆的字母 建议2:莫让常量蜕变成变量 建议3:三元操作符的类型务必一致 建议4:避免带有变长参数的方法重载 建议5:别让null值和空值威胁到变长方法    ...

  2. 转载--编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议60~64)

    阅读目录 建议60:性能考虑,数组是首选 建议61:若有必要,使用变长数组 建议62:警惕数组的浅拷贝 建议63:在明确的场景下,为集合指定初始容量 建议64:多种最值算法,适时选择 噢,它明白了,河 ...

  3. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)...

    阅读目录 建议16:易变业务使用脚本语言编写 建议17:慎用动态编译 建议18:避免instanceof非预期结果 建议19:断言绝对不是鸡肋 建议20:不要只替换一个类 回到顶部 建议16:易变业务 ...

  4. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议11~15)...

    阅读目录 建议11:养成良好习惯,显示声明UID 建议12:避免用序列化类在构造函数中为不变量赋值 建议13:避免为final变量复杂赋值 建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题 ...

  5. 转载---编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议41~46)...

    阅读目录 建议41:让多重继承成为现实 建议42:让工具类不可实例化 建议43:避免对象的浅拷贝 建议44:推荐使用序列化对象的拷贝 建议45:覆写equals方法时不要识别不出自己 建议46:equ ...

  6. 转载--编写高质量代码:改善Java程序的151个建议(第4章:字符串___建议56~59)

    阅读目录 建议56:自由选择字符串拼接方法 建议57:推荐在复杂字符串操作中使用正则表达式 建议58:强烈建议使用UTF编码 建议59:对字符串持有一种宽容的心态 回到顶部 建议56:自由选择字符串拼 ...

  7. 转载---编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议47~51)...

    阅读目录 建议47:在equals中使用getClass进行类型判断 建议48:覆写equals方法必须覆写hashCode方法 建议49:推荐覆写toString方法 建议50:使用package- ...

  8. 转载--编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议31~35)...

    阅读目录 建议31:在接口中不要存在实现代码 建议32:静态变量一定要先声明后赋值 建议33:不要覆写静态方法 建议34:构造函数尽量简化 建议35:避免在构造函数中初始化其它类 书读的多而不思考,你 ...

  9. 转载--编写高质量代码:改善Java程序的151个建议(第4章:字符串___建议52~55)

    阅读目录 建议52:推荐使用String直接量赋值 建议53:注意方法中传递的参数要求 建议54:正确使用String.StringBuffer.StringBuilder 建议55:注意字符串的位置 ...

最新文章

  1. 【面经】超硬核面经,已拿蚂蚁金服Offer!!
  2. 怎么升级浏览器_下载的chrome无法访问此网站怎么解决
  3. 好用的记事本_推荐一款可设定定时提醒的电脑桌面悬挂记事本便签软件
  4. Android 从ImageView中获取Bitmap对象方法
  5. Java编程思想之-匿名内部类
  6. 汇编工具安装三:已经配置好的汇编开发工具!
  7. vue-cli3构建项目时elementUI按需引入问题
  8. linux多线程学习(三)——线程属性设置
  9. 请描述定时器初值的计算方式_51单片机定时器初值计算器
  10. linux nginx django,如何在Linux下使用Nginx部署Django项目
  11. AD15 PCB规则检查,unplated pad
  12. sop封装与dip封装的语音芯片有何区别?
  13. AWS Landing Zone Solution
  14. 抓包工具httpbuger的使用问题
  15. 【转载】Python第三方库资源
  16. 使用每篇等工具进行图文编辑
  17. 基于vue焕心眼镜商城的设计与实现毕业设计源码091546
  18. 七种促进睡眠的好方法,让你改善睡眠质量
  19. 最短路径之迪杰斯特拉(Dijkstra)算法
  20. 软件工程师就业情况以及发展空间怎么样?

热门文章

  1. 利用zabbix api批量添加数百台监控主机
  2. setsockopt常用设置
  3. matlab解矩阵方程组
  4. VOLTE开关显示配置
  5. 计算机信息技术基础 第2版,第2章 计算机信息技术基础.ppt
  6. SurfaceView的使用以及空指针异常的处理
  7. RS232电平、CMOS电平、TTL电平是什么,区别是什么?
  8. seo网站关键词优化-搜索词和搜索结果观察_百度搜索
  9. simulinktest
  10. 从“驴肉火烧”到“烧火肉驴”