JDK1.8 新特性 (八):还在重复写空指针检查代码?
1、前言
作为一名Java程序员,无论是初入茅庐的菜鸟,还是久经江湖的高手,曾经肯定遭遇过各种各样的异常错误。在国外的一篇文章中,就统计了关于异常类型的排行榜,如下图:
是的,你没有看错,NullPointerException
位居榜首。
Null Reference
的发明者Charles Antony Richard Hoare说过:
“我称之为我的十亿美元错误。这是1965年发明空引用的结果……这导致了无数的错误,漏洞和系统崩溃,在最近40年中可能造成十亿美元的痛苦和破坏。”
这看起来有些夸张,但毫无争议的是NullPointerException
简直就是程序员心中的痛,并不是说它有多难以解决,而是为了解决它我们需要再付出了额外代价。String name = "Unknown";if (null != people) { if (null != people.getName()) { name = people.getName(); }}return name;
还记得当初刚入行时候的你,三天两头碰到NullPointerException
而引发的bug,解决完一个,又在另一个地方遇到。这也慢慢让你懂得,不要相信任何“对象”,特别是别人提供给你的,在使用的地方都加上判断,这样就放心多了。于是代码通常就变成了下面这样:
String name = "Unknown";
if (null != people) {
if (null != people.getName()) {
name = people.getName();
}
}
return name;
这样处理,虽然不用担心NullPointerException
了,但是过多的判断语句着实让人头皮发麻,代码变得臃肿不堪。如果对象过于复杂,对象里面还有对象等等,你还要继续逐层判断么?
令人兴奋的是,JDK1.8引入了一个新类java.util.Optional<T>
,凭借Optional类提供的API,我们再也不用担心NullPointerException
了,更不会再去写那些烦人的判断啦。
2、Optional类
举例来说,使用新类意味着,如果你知道一个人可能有也可能没有车,那么Person类内部的car变量就不应该声明为Car,遭遇某人没有车时把null引用赋值给它,而是应该像下图这样直接将其声明为Optional<Car>类型。
变量存在时,Optional
类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional
对象,由方法Optional.empty()
返回。Optional.empty()
方法是一个静态工厂方法,它返回Optional类的特定单一实例。
Optional
,本质上是一个容器对象,拥有一个非空值或空值,需要我们将对象实例传入该容器中。如果值存在,Optional.isPresent()
方法返回true
,并通过Optional.get()
方法获取值。
Optional
的构造方法为private
,无法直接使用new来创建Optional
对象,只能使用Optional
提供的静态方法创建。
Optional提供的创建方法如下:
-
Optional.of(obj)
:如果对象为 null,将会抛出NullPointerException
。 - Optional.ofNullable(obj):如果对象为 null,将会创建不包含值的 EMPTY Optional对象实例(new Optional<>())。
-
Optional.empty()
:等同于 Optional.ofNullable(null)。
其中,源码片段如下:
/**
* Constructs an instance with the value present.
*
* @param value the non-null value to be present
* @throws NullPointerException if value is null
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
……
/**
* Returns an {@code Optional} with the specified present non-null value.
*
* @param <T> the class of the value
* @param value the value to be present, which must be non-null
* @return an {@code Optional} with the value present
* @throws NullPointerException if value is null
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
/**
* Returns an {@code Optional} describing the specified value, if non-null,
* otherwise returns an empty {@code Optional}.
*
* @param <T> the class of the value
* @param value the possibly-null value to describe
* @return an {@code Optional} with a present value if the specified value
* is non-null, otherwise an empty {@code Optional}
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
……
/**
* Returns an empty {@code Optional} instance. No value is present for this
* Optional.
*
* @apiNote Though it may be tempting to do so, avoid testing if an object
* is empty by comparing with {@code ==} against instances returned by
* {@code Option.empty()}. There is no guarantee that it is a singleton.
* Instead, use {@link #isPresent()}.
*
* @param <T> Type of the non-existent value
* @return an empty {@code Optional}
*/
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
强烈建议使用Optional.ofNullable(obj)
方法,来创建Optional
对象,并获取对应值。
3、Optional的使用
到目前为止,你已经知道Optional
的好处了吧,但是,我们该如何使用呢?
使用可接受null的Optional
对象,即:使用静态工程方法Optional.ofNullable(obj)
,创建一个可以允许null值的Optional
对象:
Optional<People> optional = Optional.ofNullable(people);
即使people是null,optional对象也就是个空对象。
如果people不为null,根据Optional.isPresent()
方法返回true
,并通过Optional.get()
方法获取值。
为了避免NPE,
Optional.isPresent()
方法已经对null进行了判断,若存在返回true。
People p = null;if (optional.isPresent()) { p = optional.get();}
看到这里,你可能会发现这与null判断检查并无差异。
后来接触到Optional
其他API,我才发现真正体现它价值的是下面这些API。
3.1 Optional.map
从对象中获取某个属性,是最常见的操作。比如,你可能需要从people对象中获取人名。在获取人名之前,你需要检查people对象是否为null,如下所示:
String name = null;
if (null != people) {
name = people.getName();
}
String name = null;if (null != people) { name = people.getName();}
使用Optional.map
方法,可以这么写:
Optional<People> optional = Optional.ofNullable(people);Optional<String> stringOptional = optional.map(People::getName);
3.2 Optional.orElse
当一个对象为 null 时,业务上通常可以设置一个默认值,从而使流程继续下去。
String name = null != people ? people.getName() : "Unknown";
或者抛出一个异常。
if (null != people.getName()) { throw new RuntimeException();}
Optional
类提供两个方法 orElse
与 orElseThrow
,可以方便完成上面转化。
// 设置默认值
String name = optional.orElse(new People("Unknown")).getName();
// 抛出异常
String name = optional.orElseThrow(RuntimeException::new).getName();
如果 optional
为空,提供默认值或抛出异常。
3.3 Optional.filter
你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要检查人名是否为“xcbeyond”。为了以一种安全的方式进行这些操作,你首先需要判断people对象是否为null,再调用它的方法getName,如下所示:
if (null != people && "xcbeyond".equals(people.getName())) {
System.out.println("ok");
}
使用Optional类提供的方法filter,可以很好的重构:
optional.filter(people1 -> "xcbeyond".equals(people.getName()))
.ifPresent(x -> System.out.print("ok"));
4、Optional重构代码
让我们一起再看看文章开头的代码:
String name = "Unknown";
if (null != people) {
if (null != people.getName()) {
name = people.getName();
}
}
return name;
在知道了Optional之后,进行代码重构:
Optional<People> optional = Optional.ofNullable(people);
return optional.map(People::getName).orElse("Unknown");
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Nginx——ubuntu安装Nginx并配置https
- Istio 中业务开发需要关注的二三事
- MongoDB——Ubuntu安装及配置带认证的副本集(亲测)
- 经验——SpringBoot 获取 resource 目录下的文件
- 聚类热图怎么按自己的意愿调整分支的顺序?
- H5|HTTP-FLV|WS-FLV|HLS|RTMP免费直播点播播放器如何自定义层叠DIV全屏后显示在视频上方?
- 什么是时间分片(Time Slicing)?
- 48岁的C语言,你知道它背后的历史吗?
- 【技术创作101训练营】我是如何使用freemarker生成Word文件的?
- 4种主流超参数调优技术
- PyTorch 最佳实践:模型保存和加载
- 突击并发编程JUC系列-启航篇
- 拜托!别再问我多线程的这些问题了
- Tomcat 架构原理解析到架构设计借鉴
- C++核心准则T.65:使用标签分发提供函数的不同实现