如何正确的使用Java8的新特性之Optional

简介: 使用 **`Java8`** 有一段时间了,对于其中的 **`Optional`** 类使用较为频繁,所以写一篇文章记录

使用 Java8 有一段时间了,对于其中的 Optional 类使用较为频繁,所以写一篇文章记录

我不会说是因为老记不住调用 Api 才写的

Optional 类主要解决的问题是 Java 常见的的空指针异常 NullPointerException

从创建 OptionalAPI 来看,可以创建内容不为空或内容为空的类

同时,Optional 也是用来实现 函数式编程 的一个很大的进步,虽然代码精炼了,但是从 代码可读性 上来说并不友好。所以,根据实际业务场景来合理使用

场景模拟


一个学生类,其中包含姓名、班级类

班级类中包含课程类、教室位置等信息

课程类包括授课老师、课程名称等信息

假设:一个学生对应一个班级,一个班级对应一门课程
根据学生类→获取班级类→获取课程类

如果你想获取学生报的课程,那么代码是这样的

String courseName = student.getSchoolClass().getCourse().getCourseName();

这样的话是很可能发生空指针异常,如果想控制这个异常,代码是这样的

if (student != null) {
    if (student.getSchoolClass() != null) {
        SchoolClass schoolClass = student.getSchoolClass();
        if (schoolClass != null) {
            Course course = schoolClass.getCourse();
            if (course != null) {
                String courseName = course.getCourseName();
            }
        }
    }
}

这种代码逻辑上确实感觉很明了,但是总觉得有一丝丝不优雅。可以看下 Optional 类的一些使用,看能否进行简化并控制异常的抛出

创建实例


Optional 类有三个创建实例的方法,分别是 Optional.empty()Optional.of(value)Optional.ofNullable(value),看分别对应什么样的需求和场景

Optional.empty()

此方法是声明一个空的Optional

Optional<String> str = Optional.empty();

这种方式创建出来的类在被赋值前不能够被访问,如果进行访问的话将会抛出异常

java.util.NoSuchElementException: No value present

可以使用另外一种方式来防止空值传入

Optional.of(value)

依据非空值创建一个Optional,如果传入值为空,会直接抛出 NullPointerException

Optional<String> nameStr = Optional.of("百万");

Optional.ofNullable(value)

如果对象即可能是 空 也可能是非 空,那么应该使用 Optional.ofNullable(value) 方法


微信搜索【源码兴趣圈】,关注龙台,回复【资料】领取涵盖 GO、Netty、SpringCloud Alibaba、Seata、开发规范、面试宝典、数据结构等电子书 or 视频学习资料!

访问 Optional 对象的值


使用 Optional 创建出来的对象是不能够直接使用,而是需要使用 .get() 方法获取到其中的值

@Test
public void testOptionalGet() {
    String name = "马马马马马百万";
    Optional<String> opt = Optional.ofNullable(name);
    System.out.println(opt.get());
}

检查 Optional 对象不为空


检查 Optional 对象不为空有两种方式,分别是 isPresent()ifPresent()

isPresent()

如果 Optional 调用了 isPresent() 方法,那么会返回一个 boolean

@Test
public void testOptionalIsPresent() {
    String name = "马马马马马百万";
    Optional<String> opt = Optional.ofNullable(name);
    if (opt.isPresent()) {
        System.out.println(opt.get());
    }
}

ifPresent()

此方法不仅可以检查 Optional 是否为空,还有一个 Consumer(消费者) 参数,如果不为空,执行传入的 lambda 表达式

@Test
public void testOptionalGet() {
    String name = "马马马马马百万";
    Optional<String> opt = Optional.ofNullable(name);
    opt.ifPresent(data -> System.out.println(opt.get()));
}

返回默认值


如果 Optional 为空返回默认值提供了两个方法,分别是 orElse()orElseGet()

orElse()

因为 student 为空,所以 target 值为 student2
如果 student 不为空,那么返回值就会是 student 本身

@Test
public void testOptionalOrElse() {
    Student student = null;
    Student student2 = new Student("马马马马马百万", 26);
    Student target = Optional.ofNullable(student).orElse(student2);
}

orElseGet()

orElseGet() 方法在有值时返回本身,值为空时,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果

@Test
public void testOptionalOrElseGet() {
    Student student = null;
    Student target = Optional.ofNullable(student).orElseGet(() -> new Student("马马马马马百万", 26));
}

orElse() 和 orElseGet() 的不同之处

orElse()orElseGet() 在值为空时处理结果是一致的;But 在值不为空时,又是一番风景

@Test
public void testOptionalOrElse() {
    Student student = new Student();
    log.info("student orElse");
    Student result1 = Optional.ofNullable(student).orElse(createStudent());
    log.info("student orElseGet");
    Student result2 = Optional.ofNullable(student).orElseGet(() -> createStudent());
}

public Student createStudent() {
    log.info("creating student");
    return new Student("马马马马马百万", 26);
}

打印输出

student orElse
creating student
student orElseGet

通过打印日志看出,orElseGet()student 不为空时没有调用 createStudent() 方法,反观 orElse() 仍然执行了创建方法

所以不推荐使用 orElse() 作为返回默认值方法,当然在 一般情况 下,这种微量消耗不会出现问题

异常抛出 orElseThrow()


Optional 定义 orElseThrow() 作为异常抛出的 API,它会在对象为空时抛出一个异常
抛出的异常不一定非要是 RuntimeException(),也可以是其它异常或项目自定义异常等

@Test
public void testOptionalOrElseThrow() {
    String str = null;
    Optional.ofNullable(str).orElseThrow(() -> new RuntimeException());
}

相当于

@Test
public void testOptionalOrElseThrow() {
    String str = null;
    if (str == null) {
        throw new RuntimeException();
    }
}

转换值 map()


map() 在工作中是使用比较多的,先来个使用 API 看看工作流程

@Test
public void testOptionalMap() {
    Student student = new Student("马马马马马百万");
    String studentName = Optional.ofNullable(student).map(Student::getName).orElse("-");
}

map() 是可能无限级联的,像文章开始举出的例子就可以使用 map() 来解决
如果在 map() 方法的调用链中任意一环节出现空的情况,直接走 orElse("-")

@Test
public void testOptionalMap() {
    String courseName = Optional.ofNullable(student)
            .map(Student::getSchoolClass)
            .map(SchoolClass::getCourse)
            .map(Course::getCourseName)
            .orElse("-");
}

转换值 flatMap()


map()flatMap() 两个函数作用上没有什么区别,区别在于 map() 入参是 Function<? super T, ? extends U>flatMap() 入参是 Function<? super T, Optional<U>>
如果说Student获取SchoolClass的get方法是这样的

public Optional<SchoolClass> getSchoolClass() {
        return Optional.ofNullable(schoolClass);
    }

那么在获取时的代码必须要是这种形式

String courseName = Optional.ofNullable(student)
        .flatMap(Student::getSchoolClass)
        .map(SchoolClass::getCourse)
        .map(Course::getCourseName).orElse("-");

过滤值 filter()


filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional

比如有这么一个场景,我想查询学生姓名为 “张三” 的一名学生,如果查询不到返回 “-”

String studentName = Optional.ofNullable(student)
        .map(Student::getName)
        .filter(data -> Objects.equals(data, "张三"))
        .orElse("-");

使用样例


场景️️ I

检查学生的个人介绍中是否包含 特殊字符,如果包含则抛出异常

Optional.ofNullable(student)
        .map(Student::getDetails)
        .filter(Student::isDetailsValid)
        .orElseThrow(() -> new RuntimeException());
    }

场景️ II

如果学生学号 为空 执行某一操作

Optional.ofNullable(student)
        .map(Student::getStuNum)
        .ifPresent(data -> xxxx(data));

场景️ III

查看学生名字是否叫 百万 ,不是默认返回

Student student = Optional.ofNullable(student)
        .filter(data -> Objects.equals(data.getName(), "百万"))
        .orElseGet(() -> new Student("百万"));

参考文章

相关文章
|
3月前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
82 2
|
3月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
50 3
|
3月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
40 2
|
25天前
|
存储 Java 开发者
什么是java的Compact Strings特性,什么情况下使用
Java 9引入了紧凑字符串特性,优化了字符串的内存使用。它通过将字符串从UTF-16字符数组改为字节数组存储,根据内容选择更节省内存的编码方式,通常能节省10%至15%的内存。
|
1月前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
57 6
|
2月前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
39 4
|
3月前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
115 3
|
3月前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
32 2
|
3月前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
31 2
|
3月前
|
Java
Java Set以其“不重复”的特性,为我们提供了一个高效、简洁的处理唯一性约束数据的方式。
【10月更文挑战第16天】在Java编程中,Set接口确保集合中没有重复元素,每个元素都是独一无二的。HashSet基于哈希表实现,提供高效的添加、删除和查找操作;TreeSet则基于红黑树实现,不仅去重还能自动排序。通过这两个实现类,我们可以轻松处理需要唯一性约束的数据,提升代码质量和效率。
45 2