编写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期望父类的静态方法不要做破坏子类的业务行为,而覆写是将父类的的行为增强或减弱,延续父类的职责,虽然不能覆写,但可以隐藏。








0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 九个月宝宝14斤怎么办 宝宝7个月才14斤怎么办 两个月宝宝太胖怎么办 六个月的宝宝大便绿色怎么办 2岁宝宝不爱喝水怎么办 4个月宝宝厌食怎么办 1岁宝宝不爱喝水怎么办 10个月宝宝厌食怎么办 2个月宝宝厌食怎么办 宝宝吃母乳不长体重怎么办 9个月宝宝不长牙怎么办 3个月宝宝过胖怎么办 宝宝长的很慢怎么办 想一个月瘦20斤怎么办 山药弄胳膊上痒怎么办 手碰山药很痒怎么办 手摸了山药很痒怎么办 手切了山药很痒怎么办 山药弄的身上痒怎么办 疣迪去除疣体怎么办 尖锐湿庞出血了怎么办 尿道口周围烂了怎么办 尖锐湿庞复发了怎么办 宝宝脸上长湿疹怎么办如何治疗 孕妇得尖锐湿庞怎么办 痘痘留下的小坑怎么办 花洒固定座坏了怎么办 脚上起水泡烂了怎么办 月经期吃了芒果怎么办 月经量少又黑怎么办 来月经黑色的血怎么办 月经来的是黑色怎么办 来月经有血块是怎么办 月经又少又黑怎么办 来月经发黑又少怎么办 月经血发黑量少怎么办 做人流后肚子胀怎么办 怀孕见红了肚子不痛怎么办 月经来是黑色的怎么办 怀孕了长了痔疮怎么办 怀孕了有外痔疮怎么办