在Java语言在,null被分配给一个对象的引用以表示对象指向未知数据块。当应用程序使用或访问一个指向null的引用,会被抛出。 下列情况会抛出NullPointerException 。
- 调用null对象的方法。
- 访问或修改null对象的域。
- 如果null是一个数组,并获取null的长度。
- 如果null对象是一个对象数组,并访问会修改null对象的子元素。
- 如果对象是一个 Throwable值,并抛出null。
- 试图对null对象同步。
NullPointerException 是一个 RuntimeException ,Javac编译器不会强制你使用try-catch块来处理该异常。
为什么我们需要null值?
如前所述,null是一个Java中的特殊值。null在编写一些设计模式时非常有用。例如Null对象模式[1] 与单例模式。Null对象模式提供一个对象作为缺少给定类型对象的代理。单例模式确保只有一个类的实例被创建,并且提供一个对象的全局访问点。
例如,至多创建类的一个实例的一种简单的方法是声明类的所有构造函数给私有类型,并创建一个公共方法,该方法返回这个类的唯一实例。
TestSingleton.java:
import java.util.UUID;
class Singleton {
private static Singleton single = null;
private String ID = null;
private Singleton() {
/* Make it private, in order to prevent the creation of new instances of
* the Singleton class. */
ID = UUID.randomUUID().toString(); // Create a random ID.
}
public static Singleton getInstance() {
if (single == null)
single = new Singleton();
return single;
}
public String getID() {
return this.ID;
}
}
public class TestSingleton {
public static void main(String[] args) {
Singleton s = Singleton.getInstance();
System.out.println(s.getID());
}
在这个例子中,我们声明了一个Singletion类的静态实例。这个实例被在getInstance 方法中被初始化一次。注意,使用null值能使我们能够创建唯一的实例。
如何避免空NullPointerException
为了避免NullPointerException,确保所有对象在你使用之前被初始化。注意,当你声明一个变量的引用时,你正在创建一个指向一个对象的指针。在你使用该对象的方法或域之前,你必须检验指针不为null。
如果一个异常被抛出,使用在异常栈轨迹中驻留的信息。执行程序的栈轨迹由JVM提供,为了能够调试应用程序。定位产生异常的方法及异常被捕获所在的行,以找出在哪些行上,哪些引用为null。在本节剩余部分,我们将描述一些处理前文提到的异常所使用的技术。
1. 带有字面值的字符串比较
在应用程序代码中一种常见的情况是将字符串变量与字面值进行比较。字符串字面值可能是字符串或枚举元素。我们将通过使用字面值来调用方法,而不是通过使用null对象来调用方法。例如,观察下面的例子:
String str = null;
if(str.equals("Test")) {
/* The code here will not be reached, as an exception will be thrown. */
}
上面的代码片段会抛出NullPointerException。然后,如果通过字面值来调用方法,程序会正常执行。
String str = null;
if("Test".equals(str)) {
/* Correct use case. No exception will be thrown. */
}
2.检查一个方法的参数
在执行方法之前,确保检查了参数是否为null.当参数被适当检查后,方法会继续执行。否则,你可以抛出IllegalArgumentException并且通知调用方法传入的参数有误。
public static int getLength(String s) {
if (s == null)
throw new IllegalArgumentException("The argument cannot be null");
return s.length();
}
3 优先使用String.valueOf()而不是toString()
当你的应用程序代码如要一个对象的字符串来描述时,避免使用对象的toString方法。如果你的对象的引用为null,NullPointerException将会被抛出。反之,考虑使用静态方法String.valueOf(),该方法不会抛出任何异常并且在函数参数为null的情况下会打印null。
4. 使用三元运算符
三元运算符能帮助我们避免NullPointerException.运算符具有这样的形式:
boolean expression ? value1 : value2;
三元运算符能帮助我们避免NullPointerException.运算符具有这样的形式:首先,计算布尔表达式,如果表达式为true,value1被返回,否则value2被返回。我们能使用三元运算符来处理null指针,例如:
String message = (str == null) ? "" : str.substring(0, 10);
变量message将为空,如果str的引用为null,否则,如果str指向实际的数据,message将获取str的前10个字符。
5.创建返回空集合而不是null的方法
一种非常好的技术是创建一个返回空集合的方法,而不是返回null值。你的应用程序代码可以迭代空集合并使用它的方法和域,而不会抛出NullPointerException。例如:
public class Example {
private static List<Integer> numbers = null;
public static List<Integer> getList() {
if (numbers == null)
return Collections.emptyList();
else
return numbers;
}
}
6.利用Apache的 StringUtils类
Apache的Commons Lang是一个库,为java.lang API提供了帮助工具,例如字符串操作方法。StringUtils.java提供了字符串的操作,该类处理字符串对象为null的情况。你可以使用StringUtils.isNotEmpty, StringUtils.IsEmpty 及 StringUtils.equals 方法,以避免NullPointerException。
7. 使用contains(), containsKey(), containsValue() 方法
如果你的程序使用了像Maps这样的集合,考虑使用contains(), containsKey(), containsValue()方法。例如,在验证某些键存在与Map中时,返回特定键的值。
Map<String, String> map = …
…
String key = …
String value = map.get(key);
System.out.println(value.toString()); // An exception will be thrown, if the value is null.
在上面的片段中,我们不检查键是否存在与Map中,返回值可能为null.最安全的方式是:
Map<String, String> map = …
…
String key = …
if(map.containsKey(key)) {
String value = map.get(key);
System.out.println(value.toString()); // No exception will be thrown.
}
8.检查外部方法的返回值
实际环境中,使用外部的库很常见。这些库包含返回某个引用的方法。确保返回的引用不为null.阅读javadoc的方法,以更好理解函数功能与返回值。
9.使用断言
当测试代码时,断言很有用。以避免执行会抛出NullPointerException的方法。Java断言的实现通过assert关键字并抛出AssertionError.注意,你必须显式通过-ea参数启用断言。否则断言将会被忽略。
public static int getLength(String s) {
/* Ensure that the String is not null. */
assert (s != null);
return s.length();
}
如果你执行上述代码并传递null参数给getLength,将会出现下面的错误。
最后,你可以使用由Junit测试框架提供的Assert类。
存在NullPointerException的安全方法
1.访问静态成员或类方法
当你的代码试图访问静态变量或一个类的方法,即使对象的引用等于null,JVM也不会抛出NullPointerException.这是因为,在编过程中,Java编译器存储静态方法和域在特殊的位置。静态方法和域不与对象关联,而是与类名关联。
例如下面的代码不会抛出NullPointerException.
class SampleClass {
public static void printMessage() {
System.out.println("Hello from Java Code Geeks!");
}
}
public class TestStatic {
public static void main(String[] args) {
SampleClass sc = null;
sc.printMessage();
}
}
注意,尽管SampleClass的实例为null,方法还是会被执行。当方法或域为静态时,应该以“静态”的方式来访问,即通过类名来访问。例如:SampleClass.printMessage()
2. instanceof 操作符
即使对象的引用为null,instanceof操作符可使用。当引用为null时,instanceof 操作符返回false,而且不会抛出NullPointerException.例如,下面的代码:
String str = null;
if(str instanceof String)
System.out.println("It's an instance of the String class!");
else
System.out.println("Not an instance of the String class!");
结果如下:
Not an instance of the String class!
[1] Null Object pattern, https://en.wikipedia.org/wiki/Null_Object_pattern.