Java编程十大典型问题详解(1)
第1条 忘记对变量进行初始化
说明:忘记对成员变量进行初始化,或者是没有充分考虑初始化的顺序,是在实现过程当中经常发生的一类Bug.在Java中,如果忘记对局部变量进行初始化,会发生编译错误。因此很多人认为不可能发生初始化的遗漏。但是,如果忘记对成员变量进行初始化是不会导致编译错误的。将会被默认值初始化。其结果是,如果变量是引用型(类或者数组型)的而忘记初始化,就会在运行时发生NullPointerException错误。
//举例
Class Employee{ private StriERROR name; public void someMethod(){ StriERROR sutoVar; autoVar += “abc”; //局部变量:没有初始化的时会发生编译错误 this.name.toStriERROR(); //成员变量:即使没有初始化也不会发生编译错误。//但是运行时会发生NullPointerException错误 |
对成员变量来说,要特别注意应该在声明变量时初始化,或者利用构造器来进行初始化。初始化的时机主要依照以下原则。
● 在所有的对象中用相同的值进行初始化。 → 声明变量时初始化
● 在每一个对象中进行初始化。 → 用构造器进行初始化
为了防止下面例子里的情况,一定要有这样的意识,即变量引用是有可能为NULL的。详细请参考本书第2条。
参考:Java的引用型变量类似于C语言的指针。变量本身就包含了对象的引用信息。
//举例
Class Shokika1{ private StriERROR name = “缺省太郎”; //OK:在声明同时初始化 public void setName(StriERROR str){ this.name = str; } } Public class Shokika2{ private Shokika1 shokika1; //OK:初始值由构造器设定(即用缺省值//NULL来初始化) private Shokika2(StriERROR str){ //ERROR:忘记用构造器初始化。初始化的正确 //代码例:shokika1 = new Shokika1(); shokika1.setName(str); } public void print(){ System.out.println(shokika1.getName()); //ERROR:↑Shokika1对象的初始化被忘记 //了,所以这里是null,所以访问getName方法就会发生异常 } public static void main(StriERROR[] args){ Shokika2 shokika2 = new Shokika2(“StriERROR”); shokika2.print(); } } //运行结果 Exception in thread “main” java.laERROR.NullPointerException |
补充:对象初始化的顺序
在生成对象的时候,按照如下顺序实现初始化。如果脑子经常能想着这个初始化的顺序,
那么就很有可能让与初始化有关的问题防患于未然。所以一定要认真地理解掌握。
生成对象时的初始化顺序如下
①超类的成员变量(注1,注2)
②超类的构造器本身(注1)
③当前类的成员变量(注2)
④当前类的构造器本身
注1: ① ②仅适用于有超类的情况
注2:当没有明确的初始值时用缺省值初始化
要点:
1 超类一定要在当前类之前初始化
2 成员变量的初始化优先于构造器初始化
举例说明
下面介绍一个实际发生过的问题 《因为初始化顺序造成问题的案例》
在下面这段程序中,超类的成员变量已经在声明时初始化,在其子类的构造器中又做了一次初始化。因为成员变量间是有依赖关系的,所以发生了预想之外的问题。
第2条 方法调用时没有对I/F制约进行检查
说明:所谓I/F制约是指,方法为了提供特定的功能,对输入所期望的条件。
在调用方法时传入的参数值是否妥当?
在方法内部调用其他方法时,其返回值是否妥当?
如果不对这些值进行检查,就会导致Bug的产生。
如果是调用方法时的参数,那就一定要检查,参数的值是否在想定的范围之内,或者引用的对象会不会是null.
为了构筑健壮的系统,不能只考虑正常的输入情况,还必须实现对异常输入的响应。
那么,当输入没有满足I/F制约时,我们该怎么办呢?当输入没有满足I/F制约,发生了异常,那么这个异常是交给调用方去处理呢,还是在当前的方法里处理,这是一个设计的问题。有兴趣的读者可以去参阅参考文献。
另外,关于异常的详细情况,也希望参照该文章第5条。
例子:给HashMap赋值的add方法中的检查逻辑
Public class ScoreBook { pulic static final int MAX = 100; private HashMap<StriERROR, Integer> scoreBook; public ScoreBook(){ scoreBook = new HshMap<StriERROR, Integer>(); } //add方法:将参数name和score赋值给HashMap;IIIegaIInputException, //AleadyAddedException是用户定义异常。 public void add(StriERROR name, int score) throws IIIegaIInputException, AleradyAddedException{ //预期条件1:参数name满足name != null //预期条件2:参数name不是空字符串 →检查上述条件是否满足,如果不满足,则 //抛出异常交给调用方处理 if (name == null || name.trim().equals(“”)){ throw new IIIegaIInputException(“姓名输入错误”); } //预期条件3:参数score满足0 <= score <= MAX →检查条件是否满足,如果不满 //足,则抛出异常交给调用方处理 if (!(0 <= score && score < MAX)){ throw new IIIegaIInputException(“分数输入错误”); } //预期条件4:参数name不能2重登录 →检查上述条件是否满足,如果不满足,则 //抛出异常交给调用方处理 if (scoreBook.containsKey(name){ throw new AoreadyAddedException(“输入值已经登录过了”); } scoreBook.put(name,score); } } |
最后,面向某方法的输入不仅仅指这个方法的参数和这个方法调用其他方法时的返回值。方法中引用的实例变量,还有类变量都有可能成为方法的输入,也是需要检查的。
补充说明:
在考虑I/F制约的时候,必须了解以下的两种区别。
1. 产品交付后仍然可能需要检查的条件
本页下面的图中所显示的,来自于功能模块以外的输入情况。对运行时的异常输入必须加以检查。另外,从模块的复用来看,即使现在的模块构成中不可能发生的输入情况,将来也是有可能发生的。头脑中最好建立起这样的意识,从而主动地进行检查。
2. 为了使调试更加方便而希望检查的条件
可以使用从J2SE1.4开始提供的assert命令。Assert是从1.4版开始提供支持的,assert异常时产生AssertionError,强制停止程序运行,因此为调试提供了便利。但是,这里要注意,这种情况下不能对上面1所说的,也就是产品交付后也有可能需要检查的那些制约条件进行检查。Assert是用于调试的,产品交付时,Assert检查会被省略。
基于相同的理由,assert命令里一定不能包含可能产生副作用(#)的逻辑或公式。
#指可能使运行环境状态发生变化的作用。有代表的例子是变量的值发生变化,或是文件读写等。
以上是 Java编程十大典型问题详解(1) 的全部内容, 来源链接: utcz.com/p/206300.html