• Java8 Optional 的正确用法以及在 Java9 中的增强


    版本

    • JDK 8

    前言

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

    先看一个小案例

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

    Optional

    Optional 创建

    • Optional 提供三种方式来创建 Optional 对象:
            // 创建包含值的 Optional 对象
            Optional<String> nonEmptyOptional = Optional.of("Hello");
    
            // 创建一个空的 Optional 对象
            Optional<String> emptyOptional = Optional.empty();
    
            // 创建包含可能为空的值的 Optional 对象
            Optional<String> nullableOptional = Optional.ofNullable(null);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Optional 常用方法

    • isPresent():检查值是否存在。
    • get():获取值,如果值不存在会抛出 NoSuchElementException 异常。
    • orElse(T other):获取值,如果值不存在则返回指定的默认值。
    • orElseGet(Supplier other):获取值,如果值不存在则返回由 Supplier 提供的默认值。
    • orElseThrow(Supplier exceptionSupplier):获取值,如果值不存在则抛出由 Supplier 提供的异常。
            // 检查值是否存在
            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"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    orElse(T other)orElseGet(Supplier other)

    • 两者都是在值不存在时返回默认值,但还是有一些差异:1、接受参数不同 2、某些场景写法中存在性能问题(注意点)
    Optionalnull
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    Optional 不为 null
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 对比两种情况,我们发现 orElse(T other) 无论 Optional 是否 null 都会执行传入的函数获取结果值,在一些高并发的场景会造成额外的性能浪费,应尽可能选择使用 orElseGet(Supplier other)

    mapflatMap

    • mapflatMap 可以将当前值传入到参数函数中,并返回一个 Optional 对象,两者唯一的区别在于 flatMap 不会再次包装,即传入函数返回值为 Optional 类型,具体可以参考下面的例子:
        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));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 示例:
    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 了解 mapflatMap 方法十分重要,因为这是我们后文实现链式调用的关键。
    • 注:get 方法中一部分返回了 Optional 类型,一方面是为了演示,另一个方面可以通过这种方式可以让阅读我们代码的人明确的感知到这个字段的值可能为 null 你需要谨慎处理。

    filter 过滤

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

    如何优化文章开头的代码

    • 核心点在于使用 Optional 实现链式调用,首先我们需要对 User 类做一些小小的改造。
    @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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 通过使用 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编程。

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

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

  • 相关阅读:
    Codeforces Round #752 (Div. 1) B. Moderate Modular Mode
    L. Lemper Cooking Competition(前缀和/逆序对/树状数组/归并排序)
    【ASM】字节码操作 Label 如何使用 生成 if 语句
    湖仓一体电商项目(二十四):合并Iceberg小文件
    微信小程序带来了新的创业机会?
    使用注解方式实现 Redis 分布式锁
    在断更的日子里,我想了很多... ...
    【Python脚本进阶】2.1、端口扫描器(下):NMAP端口扫描
    go-GMP和Scheduler
    单目标应用:基于麻雀搜索算法SSA的微电网优化调度MATLAB
  • 原文地址:https://blog.csdn.net/qq_35578171/article/details/136737823