问题描述:
我整天都是在跟Java打交道。我在Java开发中最常用的一段代码就是用object != null在使用对象之前判断是否为空。这么做是为了避免NullPointerException。但是我发现这样检测代码实在是太丑了,而且及其不可读。
那有没有一种优雅的替代方法呢?
问题补充:
再清晰化一下我的问题,我是在强调在使用对象的属性或者方法之前,确保它不为空的重要性,就像下面这段代码一样:
if (someobject != null) {
someobject.doCalc();
}
这么些我是为了避免抛NullPointerException异常,我不知道这个对象是不是空的。正由于这些判空代码,导致我的代码血花四溅,相当惨不忍睹。
最佳解答:
对于我来说,这就是一个初级开发者走向中级开发者过程中有时候都会碰到的合理问题:他们不知道也不太信任自己所使用的约定,并且过度的去检查空值情况。另外,当他们写代码的时候,总是会让方法去返回一些值,因此就可以由方法调用方去检查空值了。
换句话说,有两种情况会出现判空语句:
- null返回值按找约定是正常的返回值
- null返回值不是正常的返回值
第二种情况很简单。可以使用assert来判断或者是允许程序报错(即抛NullPointerException)。断言是一个被充分利用的Java特性,在1.4版本中加入了这个特性。语法如下:
assert *<condition>*
或者是
assert *<condition>* : *<object>*
object的toString()输出会被包括在错误信息中。
当判断条件为false的时候assert语句就会抛出Error(AssertionError)错误。在默认情况下,Java虚拟机是不会理会断言语句的。当需要使用此特性的时候可以给JVM虚拟机传入-ea参数来启用它。同时也可以针对单个的Java类或者是包来使用断言特性。这就意味着可以在开发测试的过程中来使用断言验证代码,而在生产环境就关闭这个特性,尽管我已经测试显示断言功能并不会对应用程序产生任何影响。
这个案例中不使用断言是可以的,因为代码本身就是会报错的,就像假如你使用断言之后一定会抛出Error错误一样。用和不用的区别就是可以尽早的去发现错误,用更有意义,更加丰富的信息来描述这个错误,这样你就可以帮助你弄清楚为什么会发生这种错误(假如这种错误你确实不想它发生)。
第一种情况就要难解释一点了。如果你对你调用的代码没有控制权的话,你就惨了。如果null返回值是正常的话,那你就必须去检查它了。
如果可以控制你调用代码(当然常常还是有控制权的),那就是另一回事儿了。还是尽量的不去使用null返回值。对于返回集合的方法很简单,只需要返回空的集合就可以了,而不是null。
对于返回值不是集合的方法,就要麻烦一点了。假如碰到下面的情况:
public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}
Parse接口从标准输入中接受指令,并去执行一些操作,可能会去用这个接口实现一个命令行工具。那现在就有个约定当没找到合适的操作指令时,就返回空值。那这儿就得去验空值了。
一种可选办法就是不使用空返回值,而是空对象模式:
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?
// 这儿当然得空值判断一下啊,这儿根本就不应该出现空值
}
Action action = parser.findAction(someInput);
if (action == null) {
// do nothing
} else {
action.doSomething();
}
ParserFactory.getParser().findAction(someInput).doSomething();
明显下面这种写法更加简明清晰。
其实在findAction()方法中直接抛出更加有意义的错误信息是完全可以的。特别是你在依赖用户输入的应用中。对于findAction()方法来说抛出一个带有说明的异常要比光秃秃的抛出一个NullPointerException要好的多。
try{
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}
要是你觉得使用try/catch机制比较丑的话,那就给用户比较有意义的反馈。
public Action findAction(final String userInput){
/* Code to return requested Action if found */
return new Action(){
public void doSomething(){
userConsole.err("Action not found: "+userInput);
}
}
}