How to handle Null Pointer Exception(译)

原文地址
作者 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<Integer> numbers = null; 03      04         public static List<Integer> 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<String, String> map = … 23    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<String, String> map = … 23    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!

脚本宝典为你提供优质服务
脚本宝典 » How to handle Null Pointer Exception(译)

发表评论

提供最优质的资源集合

立即查看 了解详情