zl程序教程

您现在的位置是:首页 >  后端

当前栏目

[Java SE]Java版本特性解读:java.util.Optional [JDK1.8-]

JAVA 版本 特性 解读 util JDK1.8 SE optional
2023-09-27 14:24:41 时间

1 概述

本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空(null/empty)
OptionalJava 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。
我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致NullPointerException

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();
            }
        }
    }
}

2 样例源码

你看到了,这很容易就变得冗长,难以维护。

为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证Optional 奇迹的时刻。

WithoutOptionalTest

package test.java.util;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;

/**
 * @create-time 2022/12/29 11:00
 * @description 没有 Optional 会有什么问题?
 *  我们来模拟一个实际的应用场景。
 *  小王第一天上班,领导老马就给他安排了一个任务,要他从数据库中根据会员 ID 拉取一个会员的姓名,然后将姓名打印到控制台。
 *  虽然是新来的,但这个任务难不倒小王,于是他花了 10 分钟写下了本段代码(WithoutOptionalTest)
 * @reference-doc
 *  [1] 干货,一文彻底搞懂 Java 的 Optional - CSDN - https://blog.csdn.net/qing_gee/article/details/104767082
 */
@Slf4j
public class WithoutOptionalTest {
    /**
     * 由于当前 ID 的会员不存在,故 getMemberByIdFromDB() 返回了 null 来作为没有获取到该会员的结果。
     * 意味着:在打印会员姓名的时候要先对 mem 判空;否则,就会抛出 NPE 异常。
     * 不信?让小王把 if (mem != null) 去掉试试,控制台立马打印错误堆栈给你颜色看看。
     */
    @Test
    public void withoutOptionalTest(){
        Member mem = getMemberByIdFromDB();
        if (mem != null) {
            log.info("member.name: {}", mem.getName());
        }
        Assert.assertTrue(true);
    }

    public static Member getMemberByIdFromDB() {
        // DB查询结果:当前 ID 的会员不存在
        return null;
    }

    class Member {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

OptionalTest

package test.java.util;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * @create-time 2022/12/29 10:59
 * @description Optional 是如何解决这个问题的?
 *  小王把代码提交后,就兴高采烈地去找老马要新的任务了。
 *  本着虚心学习的态度,小王请求老马看一下自己的代码,于是老王就告诉他应该尝试一下 Optional,可以避免没有必要的 null 值检查。
 *  现在,让我们来看看小王是如何通过 Optional 来解决上述问题的。
 * @reference-doc
 *  [1] 干货,一文彻底搞懂 Java 的 Optional - CSDN - https://blog.csdn.net/qing_gee/article/details/104767082
 */
@Slf4j
public class OptionalTest {
    /**
     * getMemberByIdFromDB() 方法返回了 Optional<Member> 作为结果,这样就表明 Member 可能存在,也可能不存在,这时候就可以:
     *     在 Optional 的 ifPresent() 方法中使用 Lambda 表达式来直接打印结果。
     *
     * Optional 之所以可以解决 NPE 的问题,是因为:它明确的告诉我们,不需要对它进行判空。它就好像十字路口的路标,明确地告诉你该往哪走。
     */
    @Test
    public void withOptionalTest(){
        Optional<Member> optional = getMemberByIdFromDB();
        optional.ifPresent(mem -> {
            log.debug("member.name: {}", mem.getName());
        });
        log.debug("optional.isPresent : {}", optional.isPresent());
        log.debug("optional.get() : {}", optional.get());

        optional = Optional.empty();
        Member member = optional.orElse(new Member("optional 为空时设置的默认会员信息"));
        log.debug("member : {}", member);
        //如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
        //optional.orElseThrow(() -> { return new RuntimeException("optional为空时出现异常!"); });

        Assert.assertTrue(true);
    }

    /**
     * ofNullable() 方法内部有一个三元表达式:
     *  如果为参数为 null,则返回私有常量 EMPTY;
     *  否则,使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。
     */
    @Test
    public void ofNullableTest(){
        //true
        List<Member> list = Collections.EMPTY_LIST;
        Optional<List<Member>> optionalList = Optional.ofNullable(list);
        boolean present = optionalList.isPresent();
        log.debug("present : {}", present);
        Assert.assertTrue(true);
    }

    @Test
    public void createOptionalObject01(){
        //可以使用静态方法 empty() 创建一个空的 Optional 对象
        Optional<String> empty = Optional.empty();
        log.debug(empty.toString()); // 输出:Optional.empty

        String str = null;
        Optional<String> optionalStr = Optional.ofNullable(str);
        boolean isPresent2 = optionalStr.isPresent();
        log.debug("optionalStr : {}", optionalStr); // 输出:Optional.empty
        log.debug("isPresent2 : {}", isPresent2); // 输出:false

        Assert.assertTrue(true);
    }

    @Test
    public void createOptionalObject02(){
        //可以使用静态方法 of() 创建一个非空的 Optional 对象
        Optional<String> opt = Optional.of("沉默王二");
        log.debug(opt.toString()); // 输出:Optional[沉默王二]

        //当然了,传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException。
//        String name = null;
//        Optional<String> optnull = Optional.of(name);// java.lang.NullPointerException

        Assert.assertTrue(true);
    }

    @Test
    public void createOptionalObject03(){
        String name = null;
        Optional<String> optOrNull = Optional.ofNullable(name);
        log.debug("optOrNull : {}", optOrNull); // 输出:Optional.empty

        Assert.assertTrue(true);
    }

    @Test
    public void OrElseTest(){
        // 有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse() 和 orElseGet() 方法就派上用场了。
        // orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回 原本的值;否则返回默认值。该方法的参数类型和值得类型一致。
        String nullName = null;
        String name = Optional.ofNullable(nullName).orElse("沉默王二");
        log.debug("name : {}", name); // 输出:沉默王二

        String nullName2 = "hello";
        String name2 = Optional.ofNullable(nullName2).orElse("沉默王二");
        log.debug("name2 : {}", name2); // 输出:hello

        Assert.assertTrue(true);
    }

    @Test
    public void orElseGetTest(){
        //orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数。
        String nullName = null;
        String name = Optional.ofNullable(nullName).orElseGet(()-> { return "沉默王二"; });
        log.debug("name : {}", name); // 输出:沉默王二

        Assert.assertTrue(true);
    }

    public static Optional<Member> getMemberByIdFromDB() {
        boolean hasName = true;
        if (hasName) {
            return Optional.of(new Member("沉默王二"));
        }
        return Optional.empty();
    }

}

class Member {
    private String name;

    public Member(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // getter / setter

    @Override
    public String toString() {
        return "Member{" +
                "name='" + name + '\'' +
                '}';
    }
}

X 参考文献