NPE风险(空指针异常)
1. 前言
对于 Java 开发者来说,null
是一个令人头疼的类型,一不小心就会发生 NPE (空指针) 问题。也是 Java 语言为人诟病的一个重要原因之一。在我们消除可恶的 NPE 问题之前我们要回顾一下 Java 中 null 的概念。
2. Java 中的 null
翻译自 Oracle Java 文档[1]
Java 语言中有两种类型,一种是 基本类型 ,另一种是 引用类型。 还有一种没有名字的特殊类型,即表达式 null 。 由于 null 类型没有名称,所以不可能声明为 null 类型的变量或者转换为 null 类型。 null 引用是null 类型表达式唯一可能的值。 null 引用可以转换为任意引用类型。 事实上,程序员可以忽略null类型,可以认为null仅仅是一个可以成为任何引用类型的特殊符号。
从上面的描述我们可以了解到,其实 null 仅仅是一个关键字标识量,既不是一种类型也不算对象,无法直接声明 null 和被转换为 null,仅仅只能被引用,null 可以转换为任何引用类型。当一个 Java 引用类型对象被引用为 null 时代表当前对象不引用对象,并没有为其分配内存。 这也是我们在没有引用的对象上调用方法出现空指针的根本原因。大多数情况下 Java 开发者使用 null
是为了表示某种不存在的意思。
3. NPE 问题的解决
很多时候我们对数据是否存在有自己的期望,但是这种期望并不能直接被我们掌控,一个返回值为 null 所表达的意思并不明确过于模糊,往往通过是否判断为 null
来规避空指针问题。于是 Google 工程师在他们的 Guava
工具类库中设计了 Optional<T>
来解决 null
不可控的问题。让你在不得不使用 null
的时候,可以更加简便明确的使用 null
并帮助你避免直接使用 null
带来的问题。Java 8 将此设计吸收。我们可以直接使用 Java 提供的 Optional
来解决空指针问题。接下来我们来研究一下 Java 8 中的 Optional
。
4. Java 8 中的 Optional
Java 8 中的 Optional
是一个可选值的包装类。它的意义不仅仅帮我们简化了 NPE 问题的处理,同时也是 Java 函数式编程的一个重要辅助。我们接下来将对其 API 进行讲解以帮助你在实际开发中使用他们。
4.1 Optional 声明
Optional
只能通过静态方法来声明。它提供了三个静态方法:
empty() 返回一个值为 null
的 Optional
实例
Optional<Object> empty = Optional.empty();
of(T) 返回一个值不为 null
的 Optional
实例
Optional<String> nonNull = Optional.of("Felordcn");
ofNullable() 返回一个值可能为 null
的 Optional
实例
// value 值来自其它不确定的来源 String value = SomeApi.source(); // 可能为 null Optional<String> nullable = Optional.ofNullable(value); // 也可能不为 null Optional<String> hasValue = Optional.ofNullable(value);
ifPresent(Consumer) 如果值存在则该值被消费函数 Consumer
消费 , 否则不做任何事情。isPresent()
加强版
// 非空打印出字符串 nullable.ifPresent(System.out::println); //等同于 if (nullable.isPresent()) { System.out.println(nonNull); }
filter(Predicate) 如果值满足断言函数 Predicate
则返回该 Optional
,否则返回 Optional.empty()
Optional<String> nonNull = Optional.of("Felordcn"); Optional<String> felord = nonNull.filter(s -> s.startsWith("Felord")); // str = "Felordcn" String str = felord.get();
map(Function) 获取元素某个属性的 Optional
。如果该属性为 null
返回 Optional.empty()
,否则返回对应值的 Optional
Optional<User> userOpt = Optional.ofNullable(user); // username 为空 则为 空 Optional Optional<String> usernameOpt = userOpt.map(User::getUsername);
flatMap(Function) 有时候我们会返回 Optional<Optional<T>>
非常不便于处理,我们需要将元素展开,可使用该方法处理,参考 Stream Api 中的相关方法
orElse(other) 如果 Optional
的值存在,返回 Optional
, 否则指定一个 Optional
orElseGet(Supplier) 如果 Optional
的值存在,返回 Optional
, 否则指定一个执行 Supplier
函数来获取值
orElseThrow(Supplier<? extends Throwable>) 如果 Optional
的值存在,返回 Optional
, 否则抛出一个指定 Supplier
函数提供的异常
4.3 Java 9 中的新 API
or(Supplier) orElseGet
的改进类型。不单单返回具体的值,而可以函数式的返回 Optional
stream() 将 Optional
和 Stream
打通
ifPresentOrElse(Consumer) ifPresent
方法提供了有值后的消费逻辑而没有值的逻辑没有提供入口。新方法 ifPresentOrElse
弥补了这一缺陷
5. Optional 的使用误区
Optional
很香但是也不能滥用。一个危险的举动就是将 Optional
作为入参传递给方法。因为入参是不可控的,你无法保证入参中的 Optional
是否为 null
。这恰恰违背了 Optional
的本意。所以尽量在表达式中使用 Optional
或者在返回值中使用,而不是在方法的参数中使用 Optional
。
6. 总结
今天对 Optional
进行讲解。从 Optional
的设计本意到其常用的方法。我们也对 Optional
在 Java 9 中的新 API 进行了介绍。另外 Optional
也不是万能的,合理的使用才能发挥其优势。希望今天的文章对你有用。
参考资料
[1]
Oracle Java 文档: https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1