1. 快速介绍空指针异常,只有大家写过业务系统,一定对它不陌生。它是一个运行时错误,一般而言常见逻辑不严谨、懒散的代码风格导致。它的原因理解起来很简单,但是要避免它却不是一件容易的事。下面我记录了一些
1. 快速介绍
空指针异常,只有大家写过业务系统,一定对它不陌生。它是一个运行时错误,一般而言常见逻辑不严谨、懒散的代码风格导致。它的原因理解起来很简单,但是要避免它却不是一件容易的事。下面我记录了一些我认为比较好的实践,这些实践帮助我避免空指针异常的同时,也间接地提升了我的代码质量和工作效率。这些内容一部分来自我在 Kindle 上读过的书籍,一部分来自 StackOverflow 的建议和我自己实践的感受,这里记录下来,希望可以给大家带来一些启发。
2. 问题定义
假设一个这样的业务系统:一个任务 Task 中维持最近一次的执行记录 Execution,执行记录 Execution 维持了这一次的执行结果 Result。下面是接口定义:
public interface Task { Execution getExecution();}public interface Execution { Result getResult();}public interface Result {}
现在从数据库中查询获得了 Task,需要获得这一次的执行结果,如果存在结果,则做下一步操作,否则跳过。我们常常这样来写:
Task task = queryTaskFromDB();if (task != null) { if (task.getExecution() != null) { if (task.getExecution().getResult() != null) { doSomethingOnResult(task.getExecution().getResult()); } }}
为了逻辑上的严密,我们不得不对每一个 CURD 中 R 获得的内容进行判空处理,这样的面条代码显得非常啰嗦。
实际的开发中,能够如此细致严密地对每一个获取的类进行判空处理却不是一件容易的事情。很多时候业务代码中的定义并没有清楚的说明 Task::getExecution
到底能否可以返回为空:如果为空时返回 null 还是返回 NoSuchElementException
呢?这常常因为具体的实现的不同而发生变化。这也是 Java 中不友好的一面,因为这种设计导致难以完全贯彻面向接口编程。
3. 一些常见的最佳实践
3.1 建议#0 尝试使用 Optional 改造你的接口
以 2.1 中的简单的业务系统为例。我们尝试使用 Optional 来改造 Task
和 Execution
的接口定义:
public interface Task { Optional<Execution> getExecutionNullable();}public interface Execution { Optional<Result> getResultNullable();}public interface Result {}
Optional 表示这个方法返回的结果可能存在,也可能为空,如果存在则里面是一个给定类型的对象。
完成接口的改造后,如果要从数据库中查询 Task,并拿到它的执行结果要怎么做呢?看下面
