空指针的问题可以追溯到计算法发展史时期,同时空指针异常的情况也很多,甚至在程序运行阶段也无法避免空指针的情况。那么,在编码层面,我们需要注意哪些呢?
确认调用的的每个变量都已经被初始化
这点说起来很简单,但事实上随着业务的发展项目代码也会越来越庞大。这时候方法之间调用的关系也会越来越复杂,很难避免使用到的方法都已经明确被初始化。
所以这块单独放在这里,需要我们在编码的时候重点考虑变量存在的可能性,这其实大体上基于自己的实际编码经验。
尽量使用明确的值调用
如果已经明确某个变量(常量)的值,那么是可以安全调用它的方法的。例如对比下面的几行代码:
String a = null;
a.equal("b"); // 会产生空指针异常
"b".equal(a); // 推荐的写法
很明显使用常量去做调用这代码会更健壮一些。
尽量避免在函数中返回 NULL
当如果在编写方法中考虑返回 NULL,这个时候则需要冷静下是否真的需要这样子做。因为,通常来说会有比返回 NULL 更好的处理方式。
自动装箱需谨慎
自动装箱确实为编写程序带来很多方便,但我们在编程时候也不能滥用自动装箱。
比如,下面这个程序依然存在空指针异常隐患:
Person jack = new Person("jack");
int weight = jack.getWeight();
这种异常在我们使用一些 ORM 框架中会碰到,如果数据库对应的对象并不存在该值,而我们又在类中使用了一个基本类型与之对应,依然就会抛出空指针异常。在这种情况下就尽量使用包装类来对应,并且在使用该值时候先判断是否为空。
遍历谨防集合为空
for (int num : list) {
// for each num in list
}
及时验证外部数据
在代码运行的过程中,尤其在解析外部数据的时候可能会引发影响不到的问题。例如下面的 Json 数据
{"name": null, age: 28}
如果不处理完善,直接使用 name 属性也会导致空指针的问题。
使用第三方库加强验证
很多第三方的 Common 库都会有验证空指针的方法,例如 Guava 中针对空指针的判断有个单独的包去处理。
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
或者过滤 NULL 也会更加的方便
Joiner joiner = Joiner.on("").skipNulls();
return joiner.join("Harry", null, "Ron", "Hermione");
使用 @NotNull 或 @Nullable 注解
强烈建议多使用注解来增加代码的可读性,例如多增加 @NotNull 或 @Nullable 注解,也
可以加强代码静态检查方面可能会造成空指针的可能性,具体可以参见这里。
使用 Java8 的 Optional
很多「现代」语言都会有针对变量为空的可选链式判断,例如
Grovvy
语言有一个 ?.
的操作符,可以安全地处理潜在可能的空引用(据说 Java7 曾被建议引入这个但是并没有发布)。它是这么用的:
String version = computer?.getSoundcard()?.getUSB()?.getVersion();
虽然 Java 看起来非常的保守,但好在 Java8 中增加了 Option[T]
这个对象包来代表类型 T 的某一个值存在或者没有。
那么上面的代码可以写成
String name = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
看起来似乎有点麻烦,但相信我你会爱上这样的写法,具体可以参见这里。