还在用又臭又长的多层 if 判断空值?那我推荐你使用 Optional

版本

  • JDK 8

前言

  • Optional 是 Java 8 中引入的一个有趣类,用于表示一个值存在或不存在。它的设计目的是解决空指针异常(NullPointerException)问题,使得程序更加健壮、简洁。

先看一个小案例

  • 大家看一下下面的代码是否存在问题?聪明的小伙伴也许都看出来了,代码没有进行判空检验,异常的场景会抛出 NullPointerException 异常。
java 复制代码
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
  • 但是如果加上判空校验,那么我们代码可能就会变成下面这样又臭又长的情况:
java 复制代码
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();
            }
        }
    }
}
  • 那么我们方法可以优化上面这段代码呢?答案当然是肯定的,它就是我们今天要介绍的主角 Java8 引入的 Optional,接下来就让我们一起看看 Optional 的魅力以及如何优化上文中又臭又长的代码。

Optional

Optional 创建

  • Optional 提供三种方式来创建 Optional 对象:
java 复制代码
        // 创建包含值的 Optional 对象
        Optional<String> nonEmptyOptional = Optional.of("Hello");

        // 创建一个空的 Optional 对象
        Optional<String> emptyOptional = Optional.empty();

        // 创建包含可能为空的值的 Optional 对象
        Optional<String> nullableOptional = Optional.ofNullable(null);

Optional 常用方法

  • isPresent():检查值是否存在。
  • get():获取值,如果值不存在会抛出 NoSuchElementException 异常。
  • orElse(T other):获取值,如果值不存在则返回指定的默认值。
  • orElseGet(Supplier<? extends T> other):获取值,如果值不存在则返回由 Supplier 提供的默认值。
  • orElseThrow(Supplier<? extends X> exceptionSupplier):获取值,如果值不存在则抛出由 Supplier 提供的异常。
java 复制代码
        // 检查值是否存在
        System.out.println("nonEmptyOptional is present: " + nonEmptyOptional.isPresent());
        System.out.println("emptyOptional is present: " + emptyOptional.isPresent());
        System.out.println("nullableOptional is present: " + nullableOptional.isPresent());

        // 获取值
        System.out.println("nonEmptyOptional value: " + nonEmptyOptional.get());
        
        // 值为空是返回指定异常
        nullableOptional.orElseThrow(() -> new IllegalStateException("field is not present"));

orElse(T other)orElseGet(Supplier<? extends T> other)

  • 两者都是在值不存在时返回默认值,但还是有一些差异:1、接受参数不同 2、某些场景写法中存在性能问题(注意点)

Optionalnull

java 复制代码
public class TestMain {

    public static void main(String[] args) {
        Optional<String> nullableOptional = Optional.ofNullable(null);
        System.out.println(nullableOptional.orElse(defaultStr()));
        System.out.println(nullableOptional.orElseGet(() -> {
            System.out.println("执行 defaultStr 方法 orElseGet");
            return "defaultStr";
        }));
    }

    public static String defaultStr() {
        System.out.println("执行 defaultStr 方法");
        return "defaultStr";
    }
}

// 输出
执行 defaultStr 方法
defaultStr
执行 defaultStr 方法 orElseGet
defaultStr

Optional 不为 null

java 复制代码
public class TestMain {

    public static void main(String[] args) {
        Optional<String> nullableOptional = Optional.ofNullable("123");
        System.out.println(nullableOptional.orElse(defaultStr()));
        System.out.println(nullableOptional.orElseGet(() -> {
            System.out.println("执行 defaultStr 方法 orElseGet");
            return "defaultStr";
        }));
    }

    public static String defaultStr() {
        System.out.println("执行 defaultStr 方法");
        return "defaultStr";
    }
}

// 输出
执行 defaultStr 方法
123
123
  • 对比两种情况,我们发现 orElse(T other) 无论 Optional 是否 null 都会执行传入的函数获取结果值,在一些高并发的场景会造成额外的性能浪费,应尽可能选择使用 orElseGet(Supplier<? extends T> other)

mapflatMap

  • mapflatMap 可以将当前值传入到参数函数中,并返回一个 Optional 对象,两者唯一的区别在于 flatMap 不会再次包装,即传入函数返回值为 Optional 类型,具体可以参考下面的例子:
java 复制代码
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            // flatMap 不会主动包装为 Optional
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
  • 示例:
java 复制代码
@Builder
@Getter
class User implements Serializable {
    private String name;
    private Integer age;

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
}

public class TestMain {

    public static void main(String[] args) {
        User user = User.builder().name("ming").age(null).build();
        Optional.ofNullable(user).map(User::getName).orElse("unknown");
        Optional.ofNullable(user).flatMap(User::getAge).orElse(0);
    }
}
  • 了解 mapflatMap 方法十分重要,因为这是我们后文实现链式调用的关键。
  • 注:get 方法中一部分返回了 Optional 类型,一方面是为了演示,另一个方面可以通过这种方式可以让阅读我们代码的人明确的感知到这个字段的值可能为 null 你需要谨慎处理。

filter 过滤

  • Optional 类也提供了按条件过滤值的方法,filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional
java 复制代码
Optional.ofNullable(user).filter(t -> t.getName().contains("test")).orElse("unknown");

如何优化文章开头的代码

  • 核心点在于使用 Optional 实现链式调用,首先我们需要对 User 类做一些小小的改造。
java 复制代码
@Builder
@Getter
class User implements Serializable {
    private String name;
    private Integer age;

    private Address address;

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }

    public Address getAddress() {
        return address;
    }
}

class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }
}

class Country {
    private String isocode;

    public Optional<String> getCountry() {
        return Optional.ofNullable(isocode);
    }
}

public class TestMain {

    public static void main(String[] args) {
        User user = User.builder().name("ming").age(null).build();
        Optional.ofNullable(user).map(User::getAddress)
                .flatMap(Address::getCountry)
                .flatMap(Country::getIsocode)
                .orElse("unknown");
    }
}
  • 通过使用 Optional 重构,我们代码的可读性和健壮性都有了很大的提升。

Java9 中的增强

  • Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()
  • or():与 orElse() 和 orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。
  • ifPresentOrElse():需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable。
  • stream():通过把实例转换为 Stream 对象,让我们从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。

总结

  • Optional 是 Java 8 中引入的一个有趣类,它的设计目的是解决空指针异常(NullPointerException)问题,我们应该好好掌握它,从而让我们在程序代码中更加优雅的处理空指针异常(NullPointerException)问题,使我们的代码具备更好的可读性以及更加健壮。

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

相关推荐
芒果披萨13 分钟前
El表达式和JSTL
java·el
许野平38 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
独行soc2 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试