问题描述:
我在Java编程过程中使用的最多的就是,在使用一个对象之前,测试其是否为null,以防出现空指针异常(NPE,即NullPointerException)。但这样的代码非常丑陋,也降低了可读性。 有没有好的替代方案呢? 在获取对象的成员变量和成员方法时对其进行判null操作时有必要的,示例:
if (someObject != null) { someObject.doCalc(); }这样,我就避免了空指针异常的产生,我也不确定该对象是都为null,因此上述测试在我的代码中无处不在。
最高票回答:
这个问题在我听来,是菜鸟向中级开发人员进阶的过程中会面临的非常合理且常见的问题:他们要么不知道,要么不信任他们参与的代码中所产生的结果,因此,他们以一个防御的姿态,过度地进行着null checking。此外,当他们自己编写代码时,也倾向于通过返回null来表明什么,因此,他们编写得代码也要求调用者去为null的情况做检查。 换句话说,有以下两种null checking的情况: 1. null是合理的返回值; 2. null不是一个合理的返回值
第二种情况很好解决,用assert表达式(断言)或者允许失败(比如NPE)。断言是自从jdk1.4引入后却一直未被充分利用的java语言特性。语法如下:
assert <condition>or
assert <condition> : <object><condition>是一个布尔表达式,<object>是当错误产生时会调用其toString()方法的对象。 assert表达式当条件不为真时会抛出Error(AssertionError)。默认情况下,Java会忽略断言。你可以在jvm启动时通过传递-ea选项来启用断言,还可以为不同的class或者package来启用或关闭断言。这也就意味着,你可以在开发测试阶段通过断言来验证代码的合理性,但在生产环境关闭断言,尽管我接下来的示例会表明,断言并不会对生产环境有任何性能影响。 在2这种情况下不用断言起始也是OK的,因为代码也会报错,和你使用断言时发生的情况是一样的。唯一的区别在于,断言可能更迅速,表意更明确,还可能会有额外的信息,有助于你在代码出现意想不到的情况时弄明白为什么。
第1种情况稍微有点棘手。当你对所调用的代码毫无控制权时,你会被困住。如果null就是一种合理的输出,你就需要对其进行检查。 如果所调用的代码你能控制时,那就是另外一个故事了。避免用null作为返回值。那些返回集合的方法,这个很简单,返回空集合(或者数组)一向都比返回null要好。 返回值不是集合的情况稍微难一点。考虑下面一种情况,假设你有以下接口:
public interface Action { void doSomething(); } public interface Parser { Action findAction(String userInput); }Parser接受原始的用户输入并找到对应的事情去做,可能你在为某些事实现一个命令行接口。现在,你可能得约定好,如果没有合适的Action,你就会返回null。于是就有了你问题中的null checking。 一个可替代方案就是永远不返回null,反之,使用空对象模式(Null Object Pattern):
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(); }vs
ParserFactory.getParser().findAction(someInput).doSomethins();很明显,下面的方案更优,因为它更为简洁。
即便如此,可能对于find Action()来说,抛出带有明确错误信息的异常更为合适——尤其是那些你要依赖于用户输入的地方。方法findAction本身抛出异常会比调用者去放大该错误得到一个没有合理解释的NPE更好。
try { ParserFactory.getParser.findAction(someInput).doSomething(); } catch (ActionNotFoundException anfe) { userConsole.err(anfe.getMessage()); }或者说,你认为try/catch块太丑的话,比 Do Nothing 更好的方式是你提供给用户反馈。
public Action findAction(final String userInput) { /* Code to return requested Action if found */ return new Action() { userConsole.err("Action not found:" + userInput); } }本文翻译自StackOverflow中Java标签下的热贴——avoiding-null-statement。这篇帖子下关于这个问题还有很多有意思的讨论,比如null这个语法特性的设计,比如其他语言以及Java8中类似Optional的语法特性,还有像@NotNull @Nullable等等,感兴趣的你可以点击链接查看。