编写java程序151条建议读书笔记(4)
来源:互联网 发布:怎么看淘宝卖家信誉 编辑:程序博客网 时间:2024/06/10 12:13
建议26:提防包装类型的null值
Java引入包装类型(Wrapper Types)是为了解决基本类型的实例化问题,以便让一个基本类型也能参与到面向对象的编程世界中。而在Java5中泛型更是对基本类型说了"不",如果把一个整型放入List中,就必须使用Integer包装类型。
public class test { public static int testMethod(List<Integer> list) {int count = 0;for (int i : list) {count += i;}return count;}public static void main(String[] args) {List<Integer> list = new ArrayList<Integer>();list.add(1);list.add(null);System.out.println(testMethod(list));}}
基本类型和包装类型都是可以通过自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)自由转换的,null应该可以转换为0吗?运行之后的结果是:Exception in thread "main" java.lang.NullPointerException运行失败,报空指针异常,原因:在程序for循环中,隐含了一个拆箱过程,在此过程中包装类型转换为了基本类型。我们知道拆箱过程是通过调用包装对象的intValue方法来实现的,由于包装类型为null,访问其intValue方法报空指针异常就在所难免了。修改也很简单,加入null值检查即可,代码如下:for (Integer i : list) { count += (i != null) ? i : 0;}。上面以Integer和int为例说明了拆箱问题,其它7个包装对象的拆箱过程也存在着同样的问题。包装对象和拆箱对象可以自由转换,null值并不能转换为基本类型。谨记包装类型参与运算时,要做null值校验。建议27:谨慎包装类型的大小比较
public class test { public static void main(String[] args) {Integer i = new Integer(10);Integer j = new Integer(10);compare(i, j);}public static void compare(Integer i, Integer j) {System.out.println(i == j);System.out.println(i > j);System.out.println(i < j);}}
基本类型是可以比较大小的,其所对应的包装类型都实现了Comparable接口产生了两个Integer对象,然后比较两个的大小关系结果是三个false,1)i==j:在java中"=="是用来判断两个操作数是否有相等关系的,如果是基本类型则判断值是否相等,如果是对象则判断是否是一个对象的两个引用,也就是地址是否相等,这里很明显是两个对象,两个地址不可能相等。2)i>j 和 i<j:在Java中,">" 和 "<" 用来判断两个数字类型的大小关系,注意只能是数字类型的判断,对于Integer包装类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的(其它包装类型是根据相应的value值比较的,如doubleValue,floatValue等),那很显然,两者不肯能有大小关系的。问题清楚了,修改总是比较容易的,直接使用Integer的实例compareTo方法即可,但是这类问题的产生更应该说是习惯问题,只要是两个对象之间的比较就应该采用相应的方法,而不是通过Java的默认机制来处理,除非你确定对此非常了解。
建议28:优先使用整型池
public class test { public static void main(String[] args) {Scanner input = new Scanner(System.in);while (input.hasNextInt()) {int tempInt = input.nextInt();System.out.println("\n=====" + tempInt + " 的相等判断=====");Integer i = new Integer(tempInt);Integer j = new Integer(tempInt);System.out.println(" new 产生的对象:" + (i == j));// 基本类型转换为包装类型后比较i = tempInt;j = tempInt;System.out.println(" 基本类型转换的对象:" + (i == j));// 通过静态方法生成一个实例i = Integer.valueOf(tempInt);j = Integer.valueOf(tempInt);System.out.println(" valueOf产生的对象:" + (i == j));}}}
public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);//Integer.valueOf的源码}
当输入小于等127输出的结果为new 产生的对象:false。基本类型转换的对象:true。valueOf产生的对象:true。输入大于等于128时都是false。(1)new产生的Integer对象要生成一个新的对象,这是两个对象,地址肯定不等,比较结果为false。(2)装箱生成的对象,首先要说明的是装箱动作是通过valueOf方法实现的,也就是说后两个算法相同的,那结果肯定也是一样的,现在问题是:valueOf是如何生成对象从Integer.valueOf的源码看出如果是-128到127之间的int类型转换为Integer对象,则直接从cache数组中获得。cache是IntegerCache内部类的一个静态数组,容纳的是-128到127之间的Integer对象。通过valueOf产生包装对象时,如果int参数在-128到127之间,则直接从整型池中获得对象,不在该范围内的int类型则通过new生成包装对象。127的包装对象是直接从整型池中获得的,不管你输入多少次127这个数字,获得的对象都是同一个,那地址自然是相等的。而128超出了整型池范围,是通过new产生一个新的对象,地址不同当然也就不相等了。整型池的存在不仅仅提高了系统性能,同时也节约了内存空间,这也是我们使用整型池的原因,也就是在声明包装对象的时候使用valueOf生成,而不是通过构造函数来生成的原因。在判断对象是否相等的时候,最好使用equals方法,避免使用"=="产生非预期效果。注:通过包装类型的valueOf生成的包装实例可以显著提高空间和时间性能。
建议29:优先选择基本类型
包装类型是一个类,它提供了诸如构造方法,类型转换,比较等非常实用的功能,而且在Java5之后又实现了与基本类型的转换但无论是从安全性、性能方面来说,还是从稳定性方面来说,基本类型都是首选
public class test { public static void main(String[] args) {test c = new test();int i = 140;// 分别传递int类型和Integer类型c.testMethod(i);c.testMethod(new Integer(i));}public void testMethod(long a) {System.out.println(" 基本类型的方法被调用");}public void testMethod(Long a) {System.out.println(" 包装类型的方法被调用");}}
输出结果都是:基本类型的方法被调用。c.testMethod(i)的输出是正常的,那第二个为什么会调用testMethod(long a)方法呢?这是因为自动装箱有一个重要原则:基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转变成宽类型的包装类型。这句话比较拗口,简单的说就是,int可以加宽转变成long,然后再转变成Long对象,但不能直接转变成包装类型,注意这里指的都是自动转换,不是通过构造函数生成public class test { public static void main(String[] args) {test c = new test(); int i = 140; c.testMethod(i); } public void testMethod(Long a) { System.out.println(" 包装类型的方法被调用"); }}
程序的编译是不通过的,因为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类型的实参调用,这次成功了,与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类来获得随机数的:
public class test { public static void main(String[] args) {Random r = new Random(10000);for(int i=1; i<=4; i++){System.out.println("第"+i+"次:"+r.nextInt());}}}
在Java中,随机数的产生取决于种子这里程序中设置了种子1000在同一台机器上,甭管运行多少次,所打印的随机数都是相同的,如果没有种子,每次测试的结果是不同的。随机数和种子之间的关系遵从以下两个原则: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()方法的,两者殊途同归,没有差别。
建议31:在接口中不要存在实现代码
接口中可以声明常量,声明抽象方法,可以继承父接口,但就是不能有具体实现。因为接口是一种契约(Contract),是一种框架性协议,这表明它的实现类都是同一种类型,或者具备相似特征的一个集合体。
建议32:静态变量一定要先声明后赋值
public class test { public static int i = 1;static { i = 100; } public static void main(String[] args) { System.out.println(i); }}
输出结果为100,如果生命放在静态代码块后面那么结果就是1,静态变量是类加载时被分配到数据区(Data Area)的,它在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变。JVM初始化变量是先声明空间,然后再赋值,也就是说:在JVM中是分开执行的,等价于:int i ; //分配空间,i = 100; //赋值。静态变量是在类初始化的时候首先被加载的,JVM会去查找类中所有的静态声明,然后分配空间,注意这时候只是完成了地址空间的分配,还没有赋值,之后JVM会根据类中静态赋值(包括静态类赋值和静态块赋值)的先后顺序来执行,谁的位置最靠后谁有最终的决定权。建议33:不要覆写静态方法
覆写(Override)用来增强或减弱父类的方法和行为,但覆写是针对非静态方法(也叫做实例方法,只有生成实例才能调用的方法)的,不能针对静态方法(static修饰的方法,也叫做类方法)。
public class test { 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("我是子类静态方法");}// 覆写父类非静态方法@Overridepublic void doAnything() {System.out.println("我是子类非静态方法");}}
结果:我是子类非静态方法 我是父类静态方法。对象有两个类型:表面类型(Apparent Type)和实际类型(Actual Type),表面类型是声明的类型,实际类型是对象产生时的类型,例子中,变量base的表面类型是Base,实际类型是Sub。对于非静态方法,它是根据对象的实际类型来执行的,也就是执行了Sub类中的doAnything方法。而对于静态方法来说就比较特殊了,首先静态方法不依赖实例对象,它是通过类名来访问的;其次,可以通过对象访问静态方法,如果是通过对象访问静态方法,JVM则会通过对象的表面类型查找静态方法的入口,继而执行之。在子类中构建与父类方法相同的方法名、输入参数、输出参数、访问权限(权限可以扩大),并且父类,子类都是静态方法,此种行为叫做隐藏(Hide),它与覆写有两点不同:(1)表现形式不同:隐藏用于静态方法,覆写用于非静态方法,在代码上的表现是@Override注解可用于覆写,不可用于隐藏。
(2)职责不同:隐藏的目的是为了抛弃父类的静态方法,重现子类方法,例如我们的例子,Sub.doSomething的出现是为了遮盖父类的Base.doSomething方法,也就是i期望父类的静态方法不要做破坏子类的业务行为,而覆写是将父类的的行为增强或减弱,延续父类的职责,虽然不能覆写,但可以隐藏。
- 编写java程序151条建议读书笔记(4)
- 编写java程序151条建议读书笔记(1)
- 编写java程序151条建议读书笔记(2)
- 编写java程序151条建议读书笔记(3)
- 编写java程序151条建议读书笔记(5)
- 编写java程序151条建议读书笔记(6)
- 编写java程序151条建议读书笔记(7)
- 编写java程序151条建议读书笔记(8)
- 编写java程序151条建议读书笔记(9)
- 编写java程序151条建议读书笔记(10)
- 编写java程序151条建议读书笔记(11)
- 编写java程序151条建议读书笔记(12)
- 编写java程序151条建议读书笔记(13)
- 编写java程序151条建议读书笔记(14)
- 编写java程序151条建议读书笔记(15)
- 编写java程序151条建议读书笔记(16)
- 编写java程序151条建议读书笔记(18)
- 编写java程序151条建议读书笔记(17)
- 浅谈react受控组件与非受控组件
- Python AttributeError: ‘module’ object has no attribute ‘A’
- solr常用命令总结
- Java多线程/并发04、synchronized同步
- BZOJ 3105: [cqoi2013]新Nim游戏
- 编写java程序151条建议读书笔记(4)
- c 调用gtk剪贴板
- jquery extend 解析
- hadoop 2.7.3 单机模式
- JavaScript学习笔记20-while循环
- gitHub使用入门和github for windows的安装教程
- 百度地图定位
- LeetCode刷题(C++)——Minimum Depth if Binary Tree(Easy)
- LeetCode: Intersection of Two Linked Lists