Java8:使用 Optional 处理 null
写过 Java 程序的同学,一般都遇到过 NullPointerException
:) —— 为了不抛出这个异常,我们便会写如下的代码:
User user = getUserById(id);
if (user != null) {
String username = user.getUsername();
System.out.println("Username is: " + username); // 使用 username
}
但是很多时候,我们可能会忘记写 if (user != null)
—— 如果在开发阶段就发现那还好,但是如果在开发阶段没有测试到问题,等到上线却出了 NullPointerException
... 画面太美,我不敢继续想下去。
为了解决这种尴尬的处境,JDK 终于在 Java8 的时候加入了 Optional
类。Optional
的 javadoc 介绍:
A container object which may or may not contain a non-null value. If a value is present,
isPresent()
will returntrue
andget()
will return the value.
这是一个可以包含或者不包含非 null
值的容器。如果值存在则 isPresent()
方法会返回 true
,调用 get()
方法会返回该对象。
JDK 提供三个静态方法来构造一个 Optional
:
1.Optional.of(T value)
,该方法通过一个非 null
的 value 来构造一个 Optional
,返回的 Optional
包含了 value 这个值。对于该方法,传入的参数一定不能为 null
,否则便会抛出 NullPointerException
。
2.Optional.ofNullable(T value)
,该方法和 of
方法的区别在于,传入的参数可以为 null
—— 但是前面 javadoc 不是说 Optional
只能包含非 null
值吗?我们可以看看 ofNullable
方法的源码:
原来该方法会判断传入的参数是否为 null
,如果为 null
的话,返回的就是 Optional.empty()
。
3.Optional.empty()
,该方法用来构造一个空的 Optional
,即该 Optional
中不包含值 —— 其实底层实现还是 如果 Optional
中的 value 为 null
则该 Optional
为不包含值的状态,然后在 API 层面将 Optional
表现的不能包含 null
值,使得 Optional
只存在 包含值 和 不包含值 两种状态。
前面 javadoc 也有提到,Optional
的 isPresent()
方法用来判断是否包含值,get()
用来获取 Optional
包含的值 —— 值得注意的是,如果值不存在,即在一个Optional.empty
上调用 get()
方法的话,将会抛出 NoSuchElementException
异常。
我们假设 getUserById
已经是个客观存在的不能改变的方法,那么利用 isPresent
和 get
两个方法,我们现在能写出下面的代码:
Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
String username = user.get().getUsername();
System.out.println("Username is: " + username); // 使用 username
}
好像看着代码是优美了点 —— 但是事实上这与之前判断 null
值的代码没有本质的区别,反而用 Optional
去封装 value,增加了代码量。所以我们来看看 Optional
还提供了哪些方法,让我们更好的(以正确的姿势)使用 Optional
。
1.ifPresent
如果 Optional
中有值,则对该值调用 consumer.accept
,否则什么也不做。
所以对于上面的例子,我们可以修改为:
Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
2.orElse
如果 Optional
中有值则将其返回,否则返回 orElse
方法传入的参数。
User user = Optional
.ofNullable(getUserById(id))
.orElse(new User(0, "Unknown"));
System.out.println("Username is: " + user.getUsername());
3.orElseGet
orElseGet
与 orElse
方法的区别在于,orElseGet
方法传入的参数为一个 Supplier
接口的实现 —— 当 Optional
中有值的时候,返回值;当 Optional
中没有值的时候,返回从该 Supplier
获得的值。
User user = Optional
.ofNullable(getUserById(id))
.orElseGet(() -> new User(0, "Unknown"));
System.out.println("Username is: " + user.getUsername());
4.orElseThrow
orElseThrow
与 orElse
方法的区别在于,orElseThrow
方法当 Optional
中有值的时候,返回值;没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。
User user = Optional
.ofNullable(getUserById(id))
.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户没有找到"));
举一个 orElseThrow
的用途:在 SpringMVC 的控制器中,我们可以配置统一处理各种异常。查询某个实体时,如果数据库中有对应的记录便返回该记录,否则就可以抛出 EntityNotFoundException
,处理 EntityNotFoundException
的方法中我们就给客户端返回Http 状态码 404 和异常对应的信息 —— orElseThrow
完美的适用于这种场景。
@RequestMapping("/{id}")
public User getUser(@PathVariable Integer id) {
Optional<User> user = userService.getUserById(id);
return user.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户不存在"));
}
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
5.map
如果当前 Optional
为 Optional.empty
,则依旧返回 Optional.empty
;否则返回一个新的 Optional
,该 Optional
包含的是:函数 mapper 在以 value 作为输入时的输出值。
Optional<String> username = Optional
.ofNullable(getUserById(id))
.map(user -> user.getUsername());
System.out.println("Username is: " + username.orElse("Unknown"));
而且我们可以多次使用 map
操作:
Optional<String> username = Optional
.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.map(name -> name.toLowerCase())
.map(name -> name.replace('_', ' '));
System.out.println("Username is: " + username.orElse("Unknown"));
6.flatMap
flatMap
方法与 map
方法的区别在于,map
方法参数中的函数 mapper
输出的是值,然后 map
方法会使用 Optional.ofNullable
将其包装为 Optional
;而 flatMap
要求参数中的函数 mapper
输出的就是 Optional
。
Optional<String> username = Optional
.ofNullable(getUserById(id))
.flatMap(user -> Optional.of(user.getUsername()))
.flatMap(name -> Optional.of(name.toLowerCase()));
System.out.println("Username is: " + username.orElse("Unknown"));
7.filter
filter
方法接受一个 Predicate
来对 Optional
中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional
;否则返回 Optional.empty
。
Optional<String> username = Optional
.ofNullable(getUserById(id))
.filter(user -> user.getId() < 10)
.map(user -> user.getUsername());
System.out.println("Username is: " + username.orElse("Unknown"));
有了 Optional
,我们便可以方便且优雅的在自己的代码中处理 null
值,而不再需要一昧通过容易忘记和麻烦的 if (object != null)
来判断值不为 null
。如果你的程序还在使用 Java8 之前的 JDK,可以考虑引入 Google 的 Guava 库 —— 事实上,早在 Java6 的年代,Guava 就提供了 Optional
的实现。
号外:Java9 对 Optional
的增强
即将在今年 7 月到来的 JDK9 中,在 Optional
类中添加了三个新的方法:
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
or
方法的作用是,如果一个Optional
包含值,则返回自己;否则返回由参数 supplier 获得的Optional
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
ifPresentOrElse
方法的用途是,如果一个Optional
包含值,则对其包含的值调用函数 action,即action.accept(value)
,这与ifPresent
一致;与ifPresent
方法的区别在于,ifPresentOrElse
还有第二个参数emptyAction
—— 如果Optional
不包含值,那么ifPresentOrElse
便会调用emptyAction
,即emptyAction.run()
public Stream<T> stream()
stream
方法的作用就是将Optional
转为一个Stream
,如果该Optional
中包含值,那么就返回包含这个值的Stream
;否则返回一个空的Stream
(Stream.empty()
)。
举个例子,在 Java8,我们会写下面的代码:
// 此处 getUserById 返回的是 Optional<User>
public List<User> getUsers(Collection<Integer> userIds) {
return userIds.stream()
.map(this::getUserById) // 获得 Stream<Optional<User>>
.filter(Optional::isPresent)// 去掉不包含值的 Optional
.map(Optional::get)
.collect(Collectors.toList());
}
而有了 Optional.stream()
,我们就可以将其简化为:
public List<User> getUsers(Collection<Integer> userIds) {
return userIds.stream()
.map(this::getUserById) // 获得 Stream<Optional<User>>
.flatMap(Optional::stream) // Stream 的 flatMap 方法将多个流合成一个流
.collect(Collectors.toList());
}
Java8:使用 Optional 处理 null的更多相关文章
- java8 用Optional取代null
如何处理null 怎样做才能避免不期而至的NullPointerException呢?通常,可以在需要的地方添加null的检查(过于激进的防御式检查甚至会在不太需要的地方添加检测代码),并且添加的方式 ...
- Java8之Optional类
写在前头 今天再看阿里的Java开发手册,里面异常处理第10条提到这样一个建议. [推荐]防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景:1 ) 返回类型为基本数据类型,return 包 ...
- Java8的Optional:如何干掉空指针?
目录 Optional概述 Optional简单案例 Optional的主要方法 参考阅读 Optional概述 Optional 是个容器:它可以保存类型T的value,或者仅仅保存null.Opt ...
- JDK8新特性:使用Optional避免null导致的NullPointerException
空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写 ...
- Java8之使用Optional进行Null处理
Optional类这是Java 8新增的一个类,用以解决程序中常见的NullPointerException异常问题,本篇文章将详细介绍Optional类,以及如何用它消除代码中的null检查. 1. ...
- 用optional取代null
Java8引入了java.util.Optional<T>,它是一个封装的Optional值的类.变量存在时,Optional类只是对类简单封装.变量不存在时,缺失的值会被建模成一个空的O ...
- 《Java 8 in Action》Chapter 10:用Optional取代null
1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法.ALGOL W是第一批在堆上分配记录的类型语言之一.Hoare选择null引用这种方式,& ...
- java8中optional和.stream().map()
使用optional的好处:是一个可以包含或不可以包含非空值的容器对象,更加友好的处理程序中的空对象. Optional<T>有方法 isPresent() 和 get() 是用来检查其包 ...
- Java 8 (9) Optional取代null
NullPointerException,大家应该都见过.这是Tony Hoare在设计ALGOL W语言时提出的null引用的想法,他的设计初衷是想通过编译器的自动检测机制,确保所有使用引用的地方都 ...
随机推荐
- GNU C中的零长度数组
http://blog.csdn.net/ssdsafsdsd/article/details/8234736 在标准C和C++中,长度为0的数组是被禁止使用的.不过在GNU C中,存在一个非常奇怪的 ...
- Learn X in Y minutes Where X=c++
http://learnxinyminutes.com/docs/c++/ C++ is a systems programming language that, according to its i ...
- Linux-正则表达式的POSIX规范及流派
Linux/Unix工具与正则表达式的POSIX规范 对正则表达式有基本了解的读者,一定不会陌生『\d』.『[a-z]+』之类的表达式,前者匹配一个数字字符,后者匹配一个以上的小写英文字母.但是如果你 ...
- C#:XML操作(简单)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.X ...
- C++:SQLServer字段赋值
大前提: 1. 初始化环境 2. 创建连接实例 3. 创建记录集实例 注意点: 1.AddNews失败问题: (1)是否将CursorTypeEnum cursorType, LockTypeEnum ...
- LOSF海量小文件问题解决思路及开源库
"+++++++++++++++ LOSF 海量小文件存储和优化方案 +++++++++++++++++++++++++++++++++++++++++++++"一.问题产生原因以 ...
- chrome 浏览器插件开发
一.chrome 浏览器插件开发是什么: 1 从技术上说插件只是一个存在于本地的一个网站.所以呢在插件开发的过程中用到的技术无非是 javascript .html .css . 二.把当前活动页面的 ...
- c语言优先级和结合性
C语言的运算符众多,具有不同的优先级和结合性,我们将它们全部列了出来,方便大家对比和记忆: 优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右 ...
- sql查看所有表大小的方法
sql查看所有表大小的方法. 代码: declare @id int ) declare @pages int declare @dbname sysname ,) ,) ,) create tabl ...
- C++和C#实现剪切板数据交互
c#端由于system.windows.form自带的剪切板功能太少,所以写了一个Helper类把接口转了出来.这样就可以用不同的uint的id了. 并且自带的剪切板必须执行在[STAThread]模 ...