【IT168 技术文章】
操作空对象
这是报出的 ERROR2 错误模式。据个人项目经验,这种错误模式出现最为频繁,但是编程人员却往往很难发现,因为这种编译器发现不了的错误可能在代码运行很长时间时都不会发生,可是一旦出现,程序就会终止运行,并抛出 runtime 异常 java.lang.NullPointerException。通常有以下这些情况会导致操作空对象错误模式的发生。
调用空对象的方法
访问或修改空对象的域
访问或修改空数组对象的数组元素
同步空对象
传入空对象参数
调用空对象的方法
清单 1. 调用空 String 对象的 charAt() 方法
2 int a = 0;
3
4 if( a > 0 ) {
5 str = new String[]{ "developer " , "Works"};
6 }
7
8 char ch = str.charAt(0);
9
这是最典型的调用空对象方法的例子,调用一个未初始化的 String 对象的 chatAt() 方法。
清单 2. 调用未初始化数组成员的方法
2 try{
3 array = new Integer[] { new Integer(2/0), new Integer(3), new Integer(4) };
4 } catch ( Exception e ) {
5 //Do nothing here
6 }
7 int i = array[0].intValue();
8
数组 array 的三个 Integer 成员因为除数为 0 的异常并没有被初始化(这里只是用典型的除数为 0 的异常举例,其实实际工程中,初始化时发生的异常有时很难被发现,没有如此明显),但是接下来仍然调用其第 0 个成员的 intValue() 方法。
总结:调用空对象方法的错误非常常见,导致其出现的原因通常有两点:
在某个方法开始处定义了空对象,程序员准备在其后的代码中对其进行初始化,初始化完毕后再调用该对象的方法。但是有时由于初始化代码中的某个不常见的 if 之类的条件不成立或者 for/while 循环的条件不成立,导致接下来的赋值动作并没有进行,其结果就是之前定义的空对象并没有被初始化,然后又调用该对象的方法,从而造成了 java.lang.NullPointerException,如清单 1 所示。
初始化对象时出现了异常,但是没有对异常进行特殊处理,程序接下来继续运行,导致最终调用了该空对象的方法,如清单 2 所示。
这种代码缺陷在大型代码工程中往往很难被发现,因为编译器不会报错,而且代码在实际运行中,可能 99% 的时候 if 条件都是满足的,初始化也是成功的,所以程序员很难在测试中发现该问题,但是这种代码一旦交付到用户手中,发现一次就是灾难性的。
建议的解决方法:一定要明确知道即将引用的对象是否是空对象。如果在某个方法中需要调用某个对象,而此对象又不是在本方法中定义(如:通过参数传递),这时就很难在此方法中明确知道此对象是否为空,那么一定要在调用此对象方法之前先判断其是否为空,如果不为空,然后再调用其方法,如:if( obj != null ) { obj.method() … }。
访问或修改空对象的域
定义了某个类的对象,在没有对其初始化之前就试图访问或修改其中的域,同样会导致 java.lang.NullPointerException 异常。这种情况也非常常见,举一个比较典型的数组对象的例子,如清单 3 所示:
清单 3. 访问未初始化数组的 length
2 int a = 0;
3
4 while( a > 0 ) {
5 str = new String[]{"developer", "Works"};
6 }
7
8 System.out.println( str.length );
9
数组 str 由于某些条件并没有被初始化,但是却访问其 public final 域 length 想得到其长度。
总结:访问或修改某个空对象的域的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的数组对象没有被初始化,从而接下来访问或修改其域时产生 java.lang.NullPointerException异常。
建议的解决方法:与调用空对象的方法类似,尽量在访问或修改某些不能够明确判断是否为空对象的域之前,对其进行空对象判断,从而避免对空对象的操作。
访问或修改空数组对象的数组元素
当某个数组为空时,试图访问或修改其数组元素时都会抛出 java.lang.NullPointerException 异常。
清单 4. 访问或修改空数组对象的数组元素
2 2 System.out.println( str[0]);
3 3 str[0] = "developerWorks" ;
第 2 行和第 3 行都会导致 ERROR2 错误,其中第 2 行试图访问空数组对象 str 的第 0 个元素,第 3 行试图给空数组对象 str 的第 0 个元素赋值。
总结:访问或修改某个空数组对象的数组元素的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的数组对象没有被初始化,从而接下来访问或修改其数组元素时产生 java.lang.NullPointerException 异常。
建议的解决方法:与调用空对象的方法类似,尽量在访问或修改某些不能够明确判断是否为空空数组对象的数组元素之前,对其进行空对象判断,从而避免对空数组对象的操作。
同步空对象
清单 5. 同步空对象
2 int a = 0;
3
4 switch( a ) {
5 case 1: s = new String("developer");
6 case 2: s = new String("Works");
7 default:
8 ;
9 }
10
11 synchronized( s ){
12 ……
13 }
14
对空对象 s 进行同步。
总结:同步空对象的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的对象没有被初始化,从而接下来导致同步空对象,并产生 java.lang.NullPointerException 异常。
建议的解决方法:与调用空对象的方法类似,尽量在同步某些不能够明确判断是否为空的对象之前,对其进行空对象判断,从而避免对空对象的操作。
传入空对象参数
清单 6 传入空对象参数
2 return string.length();
3 }
4
5 public static void main(String[] args) {
6 String string = null;
7 int len = getLength( string );
8 }
9
将空 String 对象 string 传入 getLength 方法,从而导致在 getLength 方法内产生 java.lang.NullPointerException 异常。
总结:导致传入空对象参数的原因通常是在传参前忘记对参数对象是否为空进行检查,或者调用了错误的方法,或者假定接下来传参的函数允许空对象参数。
建议的解决方法:如果函数的参数为对象,并且在函数体中需要操作该参数(如:访问参数对象的方法或域,试图修改参数对象的域等),一定要在函数开始处对参数是否为空对象进行判断,如果为空则不再执行函数体,并最好作特殊处理,达到避免操作空对象的目的。