Java 8系列Java开发者的判空利器 -- Optional

Posted 善良勤劳勇敢而又聪明的老杨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8系列Java开发者的判空利器 -- Optional相关的知识,希望对你有一定的参考价值。

热门系列:


目录

1.前序

2.正文

2.1 Optional的起源

2.2 Optional使用场景

2.2.1 为什么要用?

2.2.2 什么场景用?

2.3 常用方法使用

2.3.1 empty()

2.3.2 of()

2.3.3 ofNullable()

2.3.4 isPresent()

2.3.5 get()

2.3.6 ifPresent()

2.3.7 filter()

2.3.8 map()

2.3.9 flatMap()

2.3.10 orElse()

2.3.11 orElseGet()

2.3.12 orElseThrow()

3.总结


1.前序

JDK8是Oracle在2014年3月19日发布正式版的,和JDK7(2011年7月发布)相隔了近3年(拖的时间堪比JDK7和JDK6之间的时间,与历史版本发布间隔相比排在第二位,JDK6发布是2006,JDK7与之相比之间差了5年,这两个版本发布时间间隔最长,中间发生了Oracle收购SUN的大事件,JDK6因此曾成为使用率最高的JDK,),中间因意见不统一多次延迟。

距离现在已经近6年了。但是,目前仍然有很多公司在使用此版本作为开发版本!(至少我目前所呆的公司仍在使用,各位目前常用的是哪个版本呢?欢迎留言。。。)

但是为什么现在才拿出来记录呢。。。。当然是因为,我乐意啊,,哈哈。。(其实是最近有时间了,想要做一个Java 8系列的博文集啦)

最近,我会不定期,持续输出Java 8新特性且常用的一些技术点,例如OptionalLambda表达式和函数式接口、Stream、日期时间API、重复注释、类型推断、 注解的扩展等等,与大家一起分享并记录!感兴趣,可以插个眼~~~!好了,今天先和大家一起扒一扒如题的判空类:Optional


2.正文

2.1 Optional的起源

著名的 NullPointerException 是引起系统失败最常见的原因。很久以前Google Guava项目引入了Optional作为解决空指针异常的一种方式,不赞成代码被null检查的代码污染,期望程序员写整洁的代码。受Google Guava的鼓励,Optional 现在是Java 8库的一部分。

Optional 只是一个容器,它可以保存一些类型的值或者null。它提供很多有用的方法,如官方文档API所示:

修饰符和类型方法和说明
static <T> Optional<T>empty() 返回一个空Optional实例。
booleanequals(Object obj) 指示其他某个对象是否“等于”此Optional。
Optional<T>filter(Predicate<? super T> predicate) 如果存在一个值,并且该值与给定的谓词匹配,则返回一个Optional描述值的描述,否则返回一个empty Optional
<U> Optional<U>flatMap(Function<? super T,Optional<U>> mapper) 如果存在值,则将提供的Optional-bearing映射函数应用于该值,返回该结果,否则返回empty Optional
Tget() 如果此值存在Optional,则返回该值,否则抛出NoSuchElementException
inthashCode() 返回当前值的哈希码值(如果有);如果没有值,则返回0(零)。
voidifPresent(Consumer<? super T> consumer) 如果存在值,请使用该值调用指定的使用者,否则不执行任何操作。
booleanisPresent() true如果存在值,则返回,否则返回false
<U> Optional<U>map(Function<? super T,? extends U> mapper) 如果存在值,则将提供的映射函数应用于该值,如果结果为非null,则返回Optional描述结果的描述。
static <T> Optional<T>of(T value) 返回Optional具有指定的当前非空值的。
static <T> Optional<T>ofNullable(T value) 返回Optional描述指定值的描述,如果不为null,则返回null Optional
TorElse(T other) 返回值(如果存在),否则返回other
TorElseGet(Supplier<? extends T> other) 返回值(如果存在),否则调用other并返回该调用的结果。
<X extends Throwable>
T
orElseThrow(Supplier<? extends X> exceptionSupplier) 返回包含的值(如果存在),否则抛出异常,由提供的供应商创建。
StringtoString() 返回此Optional的非空字符串表示形式,适用于调试。

但实际上,JDK1.8中Optional 总共有17个方法, 除去两个私有的构造方法, 除去equals, hashCode, toString三个方法外,;只剩下12个 Optional 提供的方法。

 

2.2 Optional使用场景

2.2.1 为什么要用?

在使用一个技术之前,我们必须要做一些思考,这样是很有必要的!能帮我更好的做到:知其然,知其所以然;(其实说简单点,就是防止少挖坑,骚年,醒醒吧!!你的bug还没有修复完~~~)

先说说为什么要用!!试想一下,你的代码或者你的小伙伴的代码中,你是否或多或少的见过类似如下的这些代码:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

//将上面的代码进行判空处理
if (user != null) 
    Address address = user.getAddress();
    if (address != null) 
        Country country = address.getCountry();
        if (country != null) 
            String isocode = country.getIsocode();
            if (isocode != null) 
                isocode = isocode.toUpperCase();
            
        
    

尤其是,当有很大一段业务逻辑处理的时候,你会发现,代码不光看起来很臃肿,并且难以阅读,可读性很差!那如果我们调整为使用optional来编写的话,可以转换成如下写法:

Optional.ofNullable(user)
		.map(User::getAddress)
		.map(Address::getCountry)
		.map(Country::getIsocode)
		.ifPresent(s-> s.toUpperCase());

如此,代码的可读性和整洁度,都好了许多!并且使用这种链式处理,也能更好的提高开发效率!这也是我们为什么需要用到optional的原因啦!

2.2.2 什么场景用?

我们写代码,在保证业务功能的正确和稳定性的基础之上,才会去考虑代码的可读性与简洁度,而不是一味的追求形式!!否则只会是华而不实,不仅容易出错,更是本末倒置!

个人总结了几点如下:

  • 当使用值为空的情况,并非源于报错时产生,可以使用Optional(因为有错误的情况,肯定是不正常的,需要处理的)

  • 对于一个对象,我们需要做判空、过滤、某些校验的时候,可以使用Optional

 

2.3 常用方法使用

方法介绍顺序,从简单的开始:

2.3.1 empty()

empty 方法返回一个不包含值的 Optional 实例, 注意不保证返回的 empty 是单例, 不要用 == 比较。

    public static<T> Optional<T> empty();

 

2.3.2 of()

返回一个 Optional 实例;代表指定的非空值, 如果传入 null 会立刻抛出空指针异常。

	public static <T> Optional<T> of(T value);

 

2.3.3 ofNullable()

  1. 返回一个 Optional 实例, 如果指定非空值则实例包含非空值, 如果传入 null 返回不包含值的 empty
	public static <T> Optional<T> ofNullable(T value);

 

2.3.4 isPresent()

isPresent 用来判断实例是否包含值, 如果不包含非空值返回 false, 否则返回 true

	public boolean isPresent();

 

2.3.5 get()

get 方法, 如果实例包含值则返回当前值, 否则抛出 NoSushElementException 异常

	public T get();

 

2.3.6 ifPresent()

ifPresent 方法作用是当实例包含值时, 来执行传入的 Consumer, 比如调用一些其他方法

public void ifPresent(Consumer<? super T> consumer);

例如如下用法:

public void testIfPresent() 
    // ifPresent 表示 Optional 中的对象存在才会执行 Consumer 接口对象中的方法

    List<String> dataList = new ArrayList<>();

    // 1. 不为空没有值的集合
    Optional.ofNullable(dataList)
            .ifPresent(t -> 
                System.out.println("1"); // 输出 1
                t.forEach(a -> System.out.println(a));
            );

 

2.3.7 filter()

filter 方法用于过滤不符合条件的值, 接收一个 Predicate 参数, 如果符合条件返回代表值的 Optional 实例, 否则返回 empty;例如:

private static List<User> userList = new ArrayList<>();

/**
 * 初始化 user 集合
 */
@Before
public void initEveryTestBefore() 
    userList.add(new User(22, "王旭", "wang.xu","123456", '1', true));
    userList.add(new User(21, "孙萍", "sun.ping","a123456", '2', false));
    userList.add(new User(23, "步传宇", "bu.zhuan.yu", "b123456", '1', false));
    userList.add(new User(18, "蔡明浩",  "cai.ming.hao","c123456", '1', true));
    userList.add(new User(17, "郭林杰", "guo.lin.jie", "d123456", '1', false));
    userList.add(new User(29, "韩凯", "han.kai", "e123456", '1', true));
    userList.add(new User(22, "韩天琪",  "han.tian.qi","f123456", '2', false));
    userList.add(new User(21, "郝玮",  "hao.wei","g123456", '2', false));
    userList.add(new User(19, "胡亚强",  "hu.ya.qing","h123456", '1', false));
    userList.add(new User(14, "季恺",  "ji.kai","i123456", '1', false));
    userList.add(new User(17, "荆帅",  "jing.shuai","j123456", '1', true));
    userList.add(new User(16, "姜有琪",  "jiang.you.qi","k123456", '1', false));
    logger.info("initEveryTestBefore, size ", userList.size());


// filter: optional 中的对象在不为空,并且满足某个条件的时候才会返回
@Test
public void testFilter() 
    // 1. 在集合中有年龄大于 18 岁的才会返回所有对象
    Optional.ofNullable(userList)
            .filter(t -> t.stream().anyMatch(u -> u.getAge() > 18))
            .ifPresent(t -> 
                t.forEach(u -> 
                    System.out.println("1:" + u.toString());
                );
            );

    // 2. 因为集合中没有年龄大于 50 岁的,因此不会返回任何对象
    Optional.ofNullable(userList)
            .filter(t -> t.stream().anyMatch(u -> u.getAge() > 50))
            .ifPresent(t -> 
                t.forEach(u -> 
                    System.out.println("2:" + u.toString());
                );
            );

 

2.3.8 map()

map 方法是链式调用避免空指针的核心方法, 当实例包含值时, 对值执行传入的 Function 逻辑, 并返回一个代表结果值的新的 Optional 实例;也就是将 optional 中的对象转成 其他对象,或者修改对象中的属性;如:

// 1. 返回 optional 中的对象年龄在 18 岁以上的
Optional.ofNullable(userList)
        .map(t -> 
            List<User> tempList = new ArrayList<>();
            t.forEach(u -> 
                if (u.getAge() > 18) 
                    tempList.add(u);
                
            );
            return tempList;
        )
        .ifPresent(t -> 
            t.forEach(u -> 
                System.out.println("1:" + u.toString());
            );
        );

 

2.3.9 flatMap()

flatMap方法是将 optional 中的对象转成 optional 对象,或者修改对象中的属性;与map方法类似。不同之处在于:

Optional.ofNullable(userList)
        .map(t -> 
            List<User> tempList = new ArrayList<>();
            t.forEach(u -> 
                if (u.getAge() > 18) 
                    tempList.add(u);
                
            );

            //不同之处
            return Optional.of(tempList);
        )

 

2.3.10 orElse()

orElse方法是如果实例包含值, 那么返回这个值, 否则返回指定的默认值, 如null

public T orElse(T other);

 

2.3.11 orElseGet()

orElseGet方法是如果实例包含值, 返回这个值;否则,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果。

public T orElseGet(Supplier<? extends T> other);

orElse与orElseGet不同之处:

public void givenPresentValue_whenCompare_thenOk() 
    User user = new User("john@gmail.com", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

输出结果:

Using orElse
Creating New User
Using orElseGet

 

2.3.12 orElseThrow()

如果实例不包含值, 调用传入的 Supplier 参数, 生成一个异常实例并抛出.这个方法通常与全局异常处理器一起使用, 当参数或者其他情况获取不到值时, 抛出自定义异常, 由异常处理器处理成通用返回结果, 返回给前端;如:

Optional.ofNullable(tempList)
        .orElseThrow(() -> runtimeException)
        .forEach(t -> System.out.println("2:" + t));

3.总结

以上即为JDK1.8新增的Optional类的常用方法及场景记录!举例的代码方面,比较零散,但主要是为了方便大家有一个功能使用和方法差别上的了解!(所以,不要在意那些鸡零狗碎的代码意义~~~~)

路漫漫,其修远兮!欢迎大家提问、留言!!!

 

微信同款:https://mp.weixin.qq.com/s/jhJBI6uK6bvCBTUxnj-LUA

以上是关于Java 8系列Java开发者的判空利器 -- Optional的主要内容,如果未能解决你的问题,请参考以下文章

Java 8系列Stream详解,看这一篇就够啦

Java 8系列收集器Collector与工具类Collectors

Java 8系列Java日期时间的新主宰者:LocalDateLocalTimeLocalDateTimeZonedDateTime

php变量的判空和类型判断

队列与循环队列的判空与判满的条件

Java 8 判空新写法。。