Optional

java 的 NPE(Null Pointer Exception)所谓的空指针异常搞的头昏脑涨, 有大佬说过 “防止 NPE,是程序员的基本修养。” 但是修养归修养,也是我们程序员最头疼的问题之一,那么我们今天就要尽可能的利用Java8的新特性Optional来尽量简化代码同时高效处理 NPE(Null Pointer Exception 空指针异常)

认识Optional并简单使用

简单来说,Opitonal 类就是 Java 提供的为了解决大家平时判断对象是否为空用 会用 null!=obj 这样的方式存在的判断,从而令人头疼导致 NPE(Null Pointer Exception 空指针异常),同时 Optional 的存在可以让代码更加简单,可读性跟高,代码写起来更高效.

正常代码,判断对象是否为空

Admin person=new Admin();
if (null==admin){
return "admin为null";
}
return person;

当我们使用Optional判断对象是否为空时:

//一、Optional判断对象是否为空
Admin admin = new Admin();
Optional<Admin> admin1 = Optional.ofNullable(admin);

神奇的Optional类

Optional类内部

首先我们先打开 Optional 的内部, 去一探究竟 先把几个创建 Optional 对象的方法提取出来:

【这些方法很重要一定要看懂哦,后面都会使用到的】

public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
//我们可以看到两个构造方格都是private 私有的
//说明 我们没办法在外面去new出来Optional对象
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
//这个静态方法大致 是创建出一个包装值为空的一个对象因为没有任何参数赋值
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//这个静态方法大致 是创建出一个包装值非空的一个对象 因为做了赋值
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//这个静态方法大致是 如果参数value为空,则创建空对象,如果不为空,则创建有参对象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
}

再做一个简单的实例展示 与上面对应:

// 1、创建一个包装对象值为空的Optional对象
Optional<String> optEmpty = Optional.empty(); // 2、创建包装对象值非空的Optional对象(使用of方法一定要保证对象非空,否则会抛异常)
Optional<String> optOf = Optional.of("optional"); // 3、创建包装对象值允许为空也可以不为空的Optional对象
Optional<String> optOfNullable1 = Optional.ofNullable(null);
Optional<String> optOfNullable2 = Optional.ofNullable("optional");

我们关于创建 Optional 对象的内部方法大致分析完毕 接下来也正式的进入 Optional 的学习与使用中。

Optional类常用的方法

Optional.get() 方法【返回对象的值】

get()方法源码:

public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

由此我们可以看到get()方法返回的是一个Optional实例值,

也就是说,源码中如果value的值不为空就会返回value,如果为空,则会直接抛出一个异常 "No value present"

测试实例代码:

Admin newAdmin = new Admin();
newAdmin.setName("get方法获取对象值");
Admin Nadmin = Optional.ofNullable(newAdmin).get(); 返回数据:
Nadmin=Admin(id=null, loginName=null, password=null, email=null, name=get方法获取对象值, mobile=null, departmentId=null, registerDate=null, lastLoginDate=null, status=null, delFlag=null)

Optional.isPresent()方法【判读是否为空】

isPresent()方法源码:

public Boolean isPresent() {
return value != null;
}

从源码上我们可以看到 isPresent方法返回的是一个true/false值,如果判断的对象不为空着返回false,为空着返回true

测试实例代码:

Admin admin3 = new Admin();
admin3.setName("isPresent方法判断是否为空");
Optional.ofNullable(admin3).ifPresent(p -> p.setName(""));

如果admin对象不为空,则会执行ifPresent方法中的函数,将对象中的name值修改为空字符串。如果对象为空则不会执行函数,并且不会抛空指针异常,optional中已经对NPE(非空验证)

Optional.filter() 方法 【过滤对象】

filter() 方法源码展示:

public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
//如果为空直接返回this
if (!isPresent())
return this; else
//判断返回本身还是空Optional
return predicate.test(value) ? this : empty();
}

接受一个对象,然后对他进行条件过滤,如果条件符合则返回 Optional 对象本身,如果不符合则返回空 Optional

测试代码实例:

Admin admin4 = new Admin();
admin4.setName("filter方法,根据条件过滤对象");
Optional<Admin> adminfilter = Optional.ofNullable(admin4).filter(p -> p.getName().equals("filter方法,根据条件过滤对象"));

Optional.map() 方法 [对象进行二次包装]

map() 方法将对应 Funcation 函数式接口中的对象,进行二次运算,封装成新的对象然后返回在 Optional 中 源码:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
//如果为空返回自己
if (!isPresent())
return empty();
else {
//否则返回用方法修饰过的Optional
return Optional.ofNullable(mapper.apply(value));
}
}

测试代码用例:

 Admin admin5 = new Admin();
Optional<String> adminFlatMap= Optional.ofNullable(admin5).map(m -> Optional.ofNullable(m.getName()).orElse("name为空"));

Optional.orElse() 方法 [为空返回对象]

如果包装对象为空的话,就执行 orElse 方法里的 value,如果非空,则返回写入对象 源码:

public T orElse(T other) {
//如果非空,返回value,如果为空,返回other
return value != null ? value : other;
}

Optional.orElseGet() 方法 [为空返回 Supplier 对象]

这个与 orElse 很相似,入参不一样,入参为 Supplier 对象,为空返回传入对象的. get() 方法,如果非空则返回当前对象 源码:

public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

测试代码实例:

Optional<Supplier<Person>> sup=Optional.ofNullable(Person::new);
//调用get()方法,此时才会调用对象的构造方法,即获得到真正对象
Optional.ofNullable(person).orElseGet(sup.get());

Supplier 对象:

Supplier 也是创建对象的一种方式, 简单来说,Suppiler 是一个接口,是类似 Spring 的懒加载,声明之后并不会占用内存,只有执行了 get() 方法之后,才会调用构造方法创建出对象创建对象的语法的话就是

语法:Supplier supPerson= Person::new

需要使用时supPerson.get()即可

Optional.orElseThrow() 方法 [为空返回异常]

如果对象为空,就抛出自定义的异常,如果不为空则返回当前对象,方便异常的处理:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

测试代码用例:

//简单的一个查询
Member member = memberService.selectByPhone(request.getPhone());
Optional.ofNullable(member).orElseThrow(() -> new ServiceException("没有查询的相关数据"));

相似方法区别

orElse() 和 orElseGet() 和 orElseThrow() 的异同点

方法效果类似,如果对象不为空,则返回对象,如果为空,则返回方法体中的对应参数,所以可以看出这三个方法体中参数是不一样的

  • orElse(T 对象)
  • orElseGet(Supplier 对象)
  • orElseThrow(异常)

orEle()

optional值不存在时,程序执行orElse(),返回执行后的参数,如果optional值存在时,则orElse()则不会再执行。

对于orElse()orElseGet()方法的区别,我们可以通过下面optional值得情况可以看出:

  • optional有值:
import java.util.Arrays;
import java.util.List; public class orElseOrElseGetComparation {
public static void main(String[] args){
List<Integer> list = Arrays.asList(23,1,3);
int myElse = list.stream().reduce(Integer::sum).orElse(get("myElse"));
int myElseGet = list.stream().reduce(Integer::sum).orElseGet(() -> get("myElseGet"));
System.out.println("myElse的值"+myElse);
System.out.println("myElseGet的值"+myElseGet); }
public static int get(String name){
System.out.println(name+"执行了该方法");
return 1;
}
}

结果:

myElse执行了该方法
myElse的值27
myElseGet的值27
  • optinoal为空时:
import java.util.Arrays;
import java.util.List; public class orElseOrElseGetComparation {
public static void main(String[] args){
List<Integer> list = Arrays.asList();
int myElse = list.stream().reduce(Integer::sum).orElse(get("myElse"));
int myElseGet = list.stream().reduce(Integer::sum).orElseGet(() -> get("myElseGet"));
System.out.println("myElse的值"+myElse);
System.out.println("myElseGet的值"+myElseGet); }
public static int get(String name){
System.out.println(name+"执行了该方法");
return 1;
}
}

结果:

myElse执行了该方法
myElseGet执行了该方法
myElse的值1
myElseGet的值1

从上面的执行结果我们可以看出orElse()方法在不论optional是否有值都会执行,在optional为空值的情况下orElseorElseGet都会执行,当optional不为空时,orElseGet不会执行

map()和flatMap()区别

map

map 把数组流中的每一个值,使用所提供的函数执行一遍,一一对应,得到元素个数相同的数组流。

flatMap

flat是扁平的意思。它把数组流中的每一个值,使用所提供的函数执行一遍,一一对应。得到元素相同的数组流。只不过,里面的元素也是一个子数组流。把这些子数组合并成一个数组以后,元素个数大概率会和原数组流的个数不同。

实例

案例:对给定单词列表 ["Hello","World"],你想返回列表["H","e","l","o","W","r","d"]

第一种方案 map

        String[] words = new String[]{"Hello","World"};
List<String[]> a = Arrays.stream(words)
.map(word -> word.split(""))
.distinct()
.collect(toList());
a.forEach(System.out::print);
代码输出为:[Ljava.lang.String;@12edcd21[Ljava.lang.String;@34c45dca
(返回一个包含两个String[]的list)

这个实现方式是由问题的,传递给map方法的lmbda每个单词生成了一个String[](String列表)。因此,map返回的流实际上是Stream<String[]> 类型的。你真正想要的是用Stream<String>来表示一个字符串。

下方图是上方代码stream的运行流程



第二种方式:flatMap(对流扁平化处理)

          String[] words = new String[]{"Hello","World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
a.forEach(System.out::print); 结果输出:HeloWrd

使用flatMap方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用map(Array::stream)时生成的单个流被合并起来,即扁平化为一个流。

下图是运用flatMapstream运行流程:

实战场景再现

场景 一

service 层中查询一个对象,返回之后判断是否为空并做处理

//查询一个对象
Member member = memberService.selectByIdNo(request.getCertificateNo());
//使用ofNullable加orElseThrow做判断和操作
Optional.ofNullable(member).orElseThrow(() -> new ServiceException("没有查询的相关数据"));

场景 二

我们可以在 dao 接口层中定义返回值时就加上 Optional 例如:我使用的是 jpa,其他也同理

public interface LocationRepository extends JpaRepository<Location, String> {
Optional<Location> findLocationById(String id);
}

然在是 Service

public TerminalVO findById(String id) {
//这个方法在dao层也是用了Optional包装了
Optional<Terminal> terminalOptional = terminalRepository.findById(id);
//直接使用isPresent()判断是否为空
if (terminalOptional.isPresent()) {
//使用get()方法获取对象值
Terminal terminal = terminalOptional.get();
//在实战中,我们已经免去了用set去赋值的繁琐,直接用BeanCopy去赋值
TerminalVO terminalVO = BeanCopyUtils.copyBean(terminal, TerminalVO.class);
//调用dao层方法返回包装后的对象
Optional<Location> location = locationRepository.findLocationById(terminal.getLocationId());
if (location.isPresent()) {
terminalVO.setFullName(location.get().getFullName());
}
return terminalVO;
}
//不要忘记抛出异常
throw new ServiceException("该终端不存在");
}

Optional 使用注意事项

Optional 真么好用,真的可以完全替代 if 判断吗?

我想这肯定是大家使用完之后 Optional 之后可能会产生的想法,答案是否定的

举一个最简单的栗子:

例子 1:

如果我只想判断对象的某一个变量是否为空并且做出判断呢?

Person person=new Person();
person.setName("");
persion.setAge(2);
//普通判断
if(StringUtils.isNotBlank(person.getName())){
//名称不为空执行代码块
}
//使用Optional做判断
Optional.ofNullable(person).map(p -> p.getName()).orElse("name为空");

我觉得这个例子就能很好的说明这个问题,只是一个很简单判断,如果用了 Optional 我们还需要考虑包装值,考虑代码书写,考虑方法调用,虽然只有一行,但是可读性并不好,如果别的程序员去读,我觉得肯定没有 if 看的明显.

jdk1.9 对 Optional 优化(待补充)

首先增加了三个方法:

or()、ifPresentOrElse() 和 stream()

or() orElse 等方法相似,如果对象不为空返回对象,如果为空则返回 or() 方法中预设的值。

ifPresentOrElse() 方法有两个参数:一个Consumer和一个 Runnable。如果对象不为空,会执行 Consumer 的动作,否则运行 Runnable。相比 ifPresent()多了 OrElse 判断。

stream() Optional 转换成 stream,如果有值就返回包含值的 stream,如果没值,就返回空的 stream

JAVA8新特性Optional,非空判断的更多相关文章

  1. Java8 新特性 Optional 类

    Optional 类的简介   Optional类的是来自谷歌Guava的启发,然后就加入到Java8新特性中去了.Optional类主要就是为子决解价值亿万的错误,空指针异常.   Optional ...

  2. Java8 新特性 Stream 非短路终端操作

    非短路终端操作 Java8 新特性 Stream 练习实例 非短路终端操作,就是所有的元素都遍厉完,直到最后才结束.用来收集成自己想要的数据. 方法有: 遍厉 forEach 归约 reduce 最大 ...

  3. Java8新特性——Optional

    前言 在开发中,我们常常需要对一个引用进行判空以防止空指针异常的出现.Java8引入了Optional类,为的就是优雅地处理判空等问题.现在也有很多类库在使用Optional封装返回值,比如Sprin ...

  4. java8 新特性 -Optional的常见用法

    1. Optional 一. 简介 Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题.本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为 ...

  5. Java8新特性--Optional

    Java 8引入了一个新的Optional类.Optional类的Javadoc描述如下: 这是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会 ...

  6. Java8新特性Optional、接口中的默认方法与静态方法

    Optional Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念 ...

  7. Java8新特性 - Optional容器类

    Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念.并且可以避免空指针异 ...

  8. java8 新特性 Optional容器类

    public class Godness { private String name; public Godness() { } public Godness(String name) { this. ...

  9. Java8新特性——Optional类的使用(有效的避免空指针异常)

    OPtional类的使用 概述 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guav ...

随机推荐

  1. DRF (学习第一部)

    目录 Web应用模式 API接口 RESTful API 规范 序列化 Django Rest_Framework 环境安装预与配置 序列化器 -Serializer Web应用模式 在开发web应用 ...

  2. 高精度算法求n阶阶乘

    1 #include "stdio.h" 2 #include "String.h" 3 #define MAX 10000 4 int f[MAX]; 5 v ...

  3. 微信小程序UI自动化:实践之后的记录01-选择工具/框架

    目录 1. 前言 2. 工具/框架/库选择 2.1 miniprogram-automator官方介绍(摘自官方哈) 小程序自动化 特性 2.2 minium官方介绍 特性 3. 如何选择 4. 对应 ...

  4. PHP获取文件拓展名的方法

    1.用strrchar()函数,查找字符串在另一字符串中最后出现的位置,并返回该位置到字符串最后的所有字符(返回结果包括点).即返回拓展名前  点  到结尾的字符,即为扩展名.注意与strchar() ...

  5. 2018-12-5 及 codeforces round 525v2

    突然发现五天没记录了,这五天学习完全没有按着正常规划进行,先罗列一下吧. 机器学习视频第一周的全部看完了. 算法导论看了几页. 参加了一次CF.rating只加了20,看来提高实力才是最关键的. C+ ...

  6. How to refresh datasource args caller[X++]

    To refresh  datasource args caller, you must add override method close on form like source code belo ...

  7. 18、Celery

    Celery 1.什么是Clelery Celery是一个简单.灵活且可靠的,处理大量消息的分布式系统 专注于实时处理的异步任务队列 同时也支持任务调度 Celery架构 Celery的架构由三部分组 ...

  8. 25类Android常用开源框架

    1.图片加载,缓存,处理 框架名称 功能描述 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库,已过时 Picasso 一个强大的图片下载与缓存的库 F ...

  9. Jmeter-全局变量跨线程组使用

    一.前言 前面讲了如何使用正则表达式提取值,一般提取的值在同一个线程里,随意哪个请求都是可以引用的,那如果别的线程组也想引用怎么办呢?这时就涉及到一个全局变量的知识点了,话不多说,直接实例走起. 二. ...

  10. Spring Cloud 整合分布式链路追踪系统Sleuth和ZipKin实战,分析系统瓶颈

    导读 微服务架构中,是否遇到过这种情况,服务间调用链过长,导致性能迟迟上不去,不知道哪里出问题了,巴拉巴拉....,回归正题,今天我们使用SpringCloud组件,来分析一下微服务架构中系统调用的瓶 ...