Java8使用 Optional 处理 null

转载自  Java8(5):使用 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 return true and get() 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 类中添加了三个新的方法:

  1. public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

or 方法的作用是,如果一个 Optional 包含值,则返回自己;否则返回由参数 supplier 获得的 Optional

  1. public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()

  1. public Stream<T> stream()

stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream;否则返回一个空的 StreamStream.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());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 7.1先睹为快(第一部分)

自2003年以来&#xff0c;Microsoft首次考虑对C#使用带小数点后位数的版本。当前暂定下一个版本是C# 7.1&#xff0c;其中有望包括&#xff1a;异步Main函数&#xff08;Async Main&#xff09;、默认表达式&#xff08;Default Expression&#xff09;、推导元组名&#xff08…

袜子商店应用:一个云原生参照应用

本文要点 袜子商店应用始于一个简单的演示应用&#xff0c;之后发现它十分有用&#xff0c;最终演化成一个完全容器化的、云原生参照应用。该应用混合使用了Go、Java、Spring以及Node.js。它拥有完整的持续集成和发布管道&#xff0c;最终会发布到AWS上Kubernetes集群的准生产…

最值得程序员get的30本行业干货

转载自 最值得程序员get的30本行业干货 1、互联网人的焦虑 互联网人是最焦虑的那批人&#xff0c;也是最爱学习的那批人。没办法&#xff0c;互联网行业的节奏实在太快了&#xff0c;每天都生活在信息爆炸的环境里&#xff0c;“风口”一个接一个。 网约车还没追上&#x…

编写高性能 .NET 代码 第二章:垃圾回收

垃圾回收是你开发工作中要了解的最重要的事情。它是造成性能问题里最显著的原因&#xff0c;但只要你保持持续的关注&#xff08;代码审查&#xff0c;监控数据&#xff09;就可以很快修复这些问题。我这里说的“显著的原因”&#xff0c;实际上是我们对垃圾回收的理解和期望不…

java中判断一个字符在字符串中出现的次数

源代码&#xff1a; //java中判断一个字符出现的次数//在下面字符串中查找有几个啊public static void testFindChar(){String str "啊&#xff01;我爱你中国&#xff01;啊&#xff0c;我爱你故乡";//存放每个字符的数组String [] strs new String[str.length()…

深入浅出 Java 中的包装类

转载自 深入浅出 Java 中的包装类 前阵子&#xff0c;我们分享了《Java中的基本数据类型转换》这篇文章&#xff0c;对许多粉丝还是有带来帮助的&#xff0c;今天讲一下 Java 包装类的的由来&#xff0c;及自动装箱、拆箱的概念和原理。 什么是包装类型 Java 设计当初就提…

java中实现将一个数字字符串转换成逗号分隔的数字串, 即从右边开始每三个数字用逗号分隔

源代码如下&#xff1a; /*将一个数字字符串转换成逗号分隔的数字串&#xff0c;即从右边开始每三个数字用逗号分隔 */public static void testFenGeNumber(){String number "1235954";StringBuffer sb new StringBuffer(number);for(int i number.length()-3;i&g…

事件总线(Event Bus)知多少

1. 引言 事件总线这个概念对你来说可能很陌生&#xff0c;但提到观察者&#xff08;发布-订阅&#xff09;模式&#xff0c;你也许就很熟悉。事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制&#xff0c;允许不同的组件之间进行彼此通信而又不需要相互依赖&…

影响程序员生涯的三个错误观念,你千万不要犯

转载自 影响程序员生涯的三个错误观念&#xff0c;你千万不要犯 程序员在社会上&#xff0c;到底是怎样一个生活群体&#xff1f;是否能找到自己方向&#xff1f;其实&#xff0c;路一直都在那里&#xff0c;只是你看不到而已&#xff01; 当初的你&#xff0c;可能一直被一…

用 docker secrets 保存 appsettings.Production.json

这是我们使用阿里云容器服务基于 docker 容器部署 asp.net core 应用遇到的另一个问题 —— 如果将包含敏感信息的应用配置文件 appsettings.Production.json 传递给运行在容器中的 asp.net core 应用。 Docker 针对这样的应用场景已经提供了解决方案 —— Docker Secrets&…

人脸识别简要说明

近日&#xff0c;或许是毕业季来临&#xff0c;或许是研究人脸识别的同行增多。总之&#xff0c;通过博客找我的人可所谓“络绎不绝”。 这几年来&#xff0c;自己不断的抽些碎片时间&#xff0c;整理出来的人脸识别各个版本&#xff0c;于2017年9月26日发布的java的第一个版本…

读《代码不朽:编写可维护软件的10大要则》C# 版

这本书特别针对没有接受过计算机科学或软件工程专业学习的软件开发人员&#xff0c;这类人员除了熟悉所用语言语法和语义之外&#xff0c;很少接受其他专业培训&#xff0c;对软件工程中的一些概念理解欠缺。软件设计方面考虑较少。如果要成为一个专业的程序员&#xff0c;就需…

mysql多表查询的分类

内连接 等值连接 非等值连接 自连接 外连接 inner join 等值连接 非等值连接 自连接 外连接

再有人问你volatile是什么,把这篇文章也发给他(深入分析)

转载自 再有人问你volatile是什么&#xff0c;把这篇文章也发给他 在上一篇文章中&#xff0c;我们围绕volatile关键字做了很多阐述&#xff0c;主要介绍了volatile的用法、原理以及特性。在上一篇文章中&#xff0c;我提到过&#xff1a;volatile只能保证可见性和有序性&…

Cockroach DB 1.0发布

分布式SQL数据库Cockroach DB遵循软件产品以动物命名的模式。近日&#xff0c;该数据库的第一个生产就绪版本1.0发布。 许多人将Cockroach DB视为Google Spanner的开源版本。后者是一个强一致性、横向可扩展的RDBMS&#xff0c;它起初是一个服务于谷歌服务的内部项目&#xff…