我经常会使用 object != null 来避免 NullPointerException。是不是有更好的选择?

例如:

if (someobject != null) {
someobject.doCalc();
}

上面这段代码可以避免在 someobject 是 null 的时候产生 NullPointerException。

回答

这是程序员常常遇到的问题,他们常常会过度地做空值检查,并且在自己编写的代码中也会返回 null 来表示某种特殊的含义,别人在调用他们的代码的时候也需要做空值检查。

一般说来有下面两种情况需要检查 null 值:1) null 是合法的返回值,它有某种特殊的含义。2) null 是非法的返回值。

第 2) 种情况比较好处理,可以使用 assert 语句或者直接产生错误(例如抛出 NullPointerException)。断言是 Java 1.4 引入的一个功能,但是很少看到有人使用,语法如下:

assert

assert :

其中 condition 是一个布尔表达式,object 是一个对象,它的 toString() 方法的输出将会包含在错误信息中。

如果 assert 语句的 condition 的值不是 true,那么将会抛出

Error(AssertionError)。默认情况下,Java 是关闭了断言功能的,如果要打开断言,需要在在运行的时候使用 -ea

参数通知虚拟机。还可以为指定的类或包打开或关闭断言功能。一般来说,我们可以在开发和测试的时候开启断言功能,在正式的产品中关闭断言功能。

当然也可以不使用断言,因为使用断言会让程序运行失败抛出错误,好处是可以产生可以理解的错误信息帮助你定位错误产生的位置。是否要使用 assert 取决于你自己的程序设计。

第 1) 种情况比较复杂,如果 null 是一个有效的返回值,那么就必须要做检查。

如果被调用的代码你可以控制的,那么可以尽量避免产生 null 值作为返回值。例如,返回值是集合或数组,那么可以返回一个空集合或空数组而不是直接返回 null。

如果返回值不是集合或数组,那么稍微有点复杂,看看下面的两个接口:

public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}

Parser 中的 findAction() 方法用于查找适用的 Action,这可能是用户在 UI 界面上做出的某个操作,如果没有查找到可用的 Action,这时就有可能返回 null,这时调用方就需要做空值检查。

避免返回 null 值的一种解决方案是使用空对象模式,例如:

public class MyParser implements Parser {
private static Action DO_NOTHING = new Action() {
public void doSomething() { /* do nothing */ }
};
public Action findAction(String userInput) {
// ...
if ( /* we can't find any actions */ ) {
return DO_NOTHING;
}
}
}

对比下面的调用代码:

Parser parser = ParserFactory.getParser();
if (parser == null) {
// now what?
// this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
// do nothing
} else {
action.doSomething();
}

ParserFactory.getParser().findAction(someInput).doSomething();

可见下面不需要做空值检查的代码设计更好,更加简洁高效。

当然,也可以在没有查找到 Action 的时候直接抛出对应的异常信息,这样也比直接产生 NullPointerException 更加友好。在调用的时候需要捕获这个异常:

try {
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}

或者,如你觉得 try/catch 代码很丑的话,可以在 DO_NOTHING 中直接添加没有找到 Action 时的逻辑代码:

public class MyParser implements Parser {
private static Action DO_NOTHING = new Action() {
public void doSomething() { userConsole.err("Action not found: " + userInput); }
};
public Action findAction(String userInput) {
// ...
if ( /* we can't find any actions */ ) {
return DO_NOTHING;
}
}
}