原文地址
作者 Sotirios-Efstathios (Stathis) Maneas
译者 smallclover
Thanks for your watching!
java.lang.NullPoinerException – 怎么处理空指针异常
在java中,null是一个特殊的值,它能够被赋值给对象的引用。表示该对象的值不确定。当一个应用试图使用或者访问一个引用为null的对象时,一个NullPointerException 会被抛出。以下列举的几种情况将会抛出该异常:
通过一个 null 对象调用 方法
访问或者修改一个null 对象的字段
获取null的长度,比如,一个引用值为null的数组
访问或修改一个null对象,比如 一个引用值为null的数组。
抛出 NullPointException
当你试图同步一个null对象的时候。
NullPointerException是一个RuntimeException,并且javac编译器将不会强制你使用try-catch代码块处理NullPointerException。
为什么我们需要null值?
正如前面所提到的那样,在java null值是一个特殊的值。Null值经常被使用在一些设计模式中,如Null Object Pattern 和 Singleton Pattern。前者,提供一个对象来代替当该类型的对象缺失的时候;后者确保该类有且仅有一个类的实例被创建。目的是为了提供一个该对象的全局唯一访问点。
例如,生成一个类的唯一实例的一般方法是:声明所有的构造函数为私有,然后创建一个公有的方法返回该类的唯一一个实例
TestSingleton.java:
01 import java.util.UUID;
02
03 class Singleton {
04
05 private static Singleton single = null;
06 private String ID = null;
07
08 private Singleton() {
09 /* Make it private, in order to prevent the creation of new instances of
10 * the Singleton class. */
11
12 ID = UUID.randomUUID().toString(); // Create a random ID.
13 }
14
15 public static Singleton getInstance() {
16 if (single == null)
17 single = new Singleton();
18
19 return single;
20 }
21
22 public String getID() {
23 return this.ID;
24 }
25 }
26
27 public class TestSingleton {
28 public static void main(String[] args) {
29 Singleton s = Singleton.getInstance();
30 System.out.println(s.getID());
31 }
32 }
在上面的这个例子中,我们声明了一个Singleton类的静态实例,该静态实例在getInstance内部执行最多一次的初始化。注意,这里使用null值,使得唯一的实例被创建。
怎样避免NullPointerException
为了避免出现NullPointerException,在你使用该对象之前确保所有的对象被正常的初始化。注意,当你声明了一个引用变量,你就真的创建了一个指向对象的指针。在你通过这个对象请求方法或者属性时你必须确保这个指针不是null。
另外,如果该异常被抛出,请灵活的使用堆栈跟踪中的信息,通过JVM提供的堆栈跟踪,我们能够调试应用。查找exception发生的方法以及位于该方法的多少行,然后判断指定的那一行中哪一个引用等于null。在剩余的章节,我们将具体的描述一些处理上述异常情况的技术,然而它们也并不能完全消除NullPointerException的问题,所以程序员还是应该在编写应用的时候仔细一些。
1. String与字符串常量作比较
在一个应用中经常会编写涉及到比较String变量和字符串常量的代码。这个字符串常量可能是String类型或者是Enum的一个元素。我们应该考虑使用字符串常量调用equals方法来代替使用null对象调用该方法。例如观察一下案例:
1 String str = null;
2 if(str.equals("Test")) {
3 /* The code here will not be reached, as an exception will be thrown. */
4 }
上面的代码片段将会抛出NullPointerException。然而如果我们通过字符串常量来调用equals方法,执行流程会正常进行:
1 String str = null;
2 if("Test".equals(str)) {
3 /* Correct use case. No exception will be thrown. */
4 }
2. 检查方法的参数
在执行方法的方法体之前,务必对方法的参数进行null值检查。只有在在确保属性被检查之后再继续执行函数。另外,在传递的参数有错误的时候你可以抛出一个IllegalArgumentException通知调用者。
例如:
1 public static int getLength(String s) {
2 if (s == null)
3 throw new IllegalArgumentException("The argument cannot be null");
4
5 return s.length();
6 }
3. 使用String.valueOf()方法代替toString()
当你的应用代码需要获得一个对象的字符串的表示形式,请避免使用这个对象的toString方法,如果你的对象的引用等于null,将会导致NullPointerException被抛出。我们可以考虑使用静态的String.valueOf方法,该方法不会抛出任何异常,当函数的参数为null时会打印一个“null”字符串。
译注:这里只是一个建议,具体使用什么方法还是需要对应具体的生产环境
4. 使用三元运算符
三元运算符对于我们避开NullPointerException是非常有用的。该操作符格式如下:
1 boolean expression ? value1 : value2;
首先一个boolean表达式将会被判断,如果表达式为true,value1的值将会被返回,否则,value2的值会被返回。我们使用三元表达式处理空指针,如下图所示
1 String message = (str == null) ? "" : str.substring(0, 10);
在str的引用值为null的时候message变量的值为空,否则,如果str指向实际的数据,message将会获取它前10个字符。
5. 创建一个返回空的集合的方法来代替null。
一个非常好的技巧就是创建一个能返回空集合的方法来代替null值。你的应用代码能够迭代这个空集合并且可以使用它的方法和属性,而不用担心抛出NullPointerException。例如
Example.java
01 public class Example {
02 private static List numbers = null;
03
04 public static List getList() {
05 if (numbers == null)
06 return Collections.emptyList();
07 else
08 return numbers;
09 }
10 }
6. 使用Apache的StringUtils类
apache的Commons Lang类库提供操作java.lang ApI的实用工具类,比如提供很多操作字符串的方法的类StringUtils.java,可以处理值为null的字符串。
你能使用诸如 StringUtils.isNotEmpty、StringUtils.IsEmpty和StringUtils.equals方法,为了减少NullPointerException。举个例子:
1 if (StringUtils.isNotEmpty(str)) {
2 System.out.println(str.toString());
3 }
7. 使用contains(),containsKey(),containsValue()方法
如果你的应用使用集合,例如Maps,那你应该考虑使用contains(),containsKey(),containsValue()方法,在你确定value存在于map的时候你可以根据指定的key来获取值。
1 Map map = …
2 …
3 String key = …
4 String value = map.get(key);
5 System.out.println(value.toString()); // An exception will be thrown, if the value is null.
上面的代码片段,我们没有检查是否这个key存在于map中,所以可能会返回null值,最安全的做法如下述代码所示:
1 Map map = …
2 …
3 String key = …
4 if(map.containsKey(key)) {
5 String value = map.get(key);
6 System.out.println(value.toString()); // No exception will be thrown.
7 }
8. 检查外部方法的返回值
使用第三方库在我们的日常开发中是十分常见的。当这些libraries包含的方法返回引用的时候,确保方法返回的引用不是null。另外,应该认真的阅读方法的Javadoc,为了更好的理解它的功能以及它的返回值。
9. 使用断言
当你在测试代码时使用断言是十分有用的,为了避免执行的代码片段抛出NullPointerException。Java断言通过关键字assert来执行的并且在断言出错时会抛出AssertionError。
注意你必须明确的启用断言标志在JVM中,通过执行参数-ea.否则,断言将会被完全忽略。
下面代码是一个使用断言的例子:
1 public static int getLength(String s) {
2 /* Ensure that the String is not null. */
3 assert (s != null);
4
5 return s.length();
6 }
如果你执行以上的代码片段并且传递给getLength方法一个值为null的参数,然后会有如下的错误信息被打印出来:
1 Exception in thread "main" java.lang.AssertionError
10. 单元测试
当你在测试代码的功能性和正确性的时候单元测试是极其有用的。当你的应用代码出现特定的执行流程的时候,花一些时间写一些测试案例来验证该流程不会抛出NullPointerException。
Existing NullPointerException safe methods
现有的NullPointerException异常安全的方式
1. 访问一个类的静态成员变量或者方法
当你的代码试图访问一个类的静态的变量和方法的时候,记事这个对象的引用为null,JVM也不会抛出NullPointerException。这是由于java编译器会在特别的地方存储静态的变量和字段。因此,静态字段和静态方法并不会关联到对象,而是与类的名字有关联。
例如,下述代码不会抛出NullPointerException。
TestStatic.java :
01 class SampleClass {
02
03 public static void printMessage() {
04 System.out.println("Hello from Java Code Geeks!");
05 }
06 }
07
08 public class TestStatic {
09 public static void main(String[] args) {
10 SampleClass sc = null;
11 sc.printMessage();
12 }
13 }
注意,尽管SampleClass对象的引用值为null,但是方法还是将被正确的执行。然而,当我们提到静态方法和静态字段时,最好的访问方式仍然是静态的访问方式(通过类名来访问),如SampleClass.printMessage().
2. Instanceof 操作
即使对象的引用值为null,instanceof操作也能被使用。Instanceof操作在引用的值为null的时候会返回false并且不会抛出NullPointerException。请思考一下代码片段:
1 String str = null;
2 if(str instanceof String)
3 System.out.println("It's an instance of the String class!");
4 else
5 System.out.println("Not an instance of the String class!");
执行的结果不出所料:
1 Not an instance of the String class!