Optional
概述
Doug Lea 是一位美国的计算机科学家,他是 Java 平台的并发和集合框架的主要设计者之一。他在 2014 年的一篇文章中说过:“Null sucks.”,意思是 null 很糟糕。他认为 null 是一种不明确的表示,它既可以表示一个值不存在,也可以表示一个值未知,也可以表示一个值无效。这样就会导致很多逻辑错误和空指针异常,给程序员带来很多麻烦。他建议使用 Optional 类来封装可能为空的值,从而提高代码的可读性和健壮性。
null 引用是一种表示一个对象变量没有指向任何对象的方式,它是 Java 语言中的一个特殊值,也是导致空指针异常(NullPointerException)的主要原因。虽然 null 引用可以用来表示一个值不存在或未知,也可以用来节省内存空间。但是它也不符合面向对象的思想,因为它不是一个对象,不能调用任何方法或属性。
可以看到,null 引用并不好,我们应该尽量避免使用 null,那么我们该怎么避免 null 引用引起的逻辑错误和运行时异常嘞?
其实这个问题 Java 的设计者也知道,于是他们在 Java8 之后设计引入了 Optional 类解决这个问题,本文将给大家详细介绍下 Optional 类的设计目的以及使用方法。
Optional 类是什么?
Optional
类是 java 8 中引入的一个新的类,它的作用是封装一个可能为空的值,从而避免空指针异常(NullPointerException
)。Optional
类可以看作是一个容器,它可以包含一个非空的值,也可以为空。Optional
类提供了一些方法,让我们可以更方便地处理可能为空的值,而不需要显式地进行空值检查或者使用 null。
Optional 类的设计
Optional 类的设计是基于函数式编程的思想,它借鉴了 Scala 和 Haskell 等语言中的 Option 类型。Optional 类实现了 java.util.function 包中的 Supplier、Consumer、Predicate、Function 等接口,这使得它可以和 lambda 表达式或者方法引用一起使用,形成更简洁和优雅的代码。
Optional 类被 final 修饰,因此它是一个不可变的类,它有两个静态方法用于创建 Optional 对象。
Optional.empty()
Optional.empty
表示一个空的 Optional
对象,它不包含任何值。
1 | // 创建一个空的 Optional 对象 |
Optional.of(T value)
Optional.of
表示一个非空的 Optional
对象,它包含一个非空的值。
1 | // 创建一个非空的 Optional 对象 |
Optional.ofNullable(T value)
注意,如果我们使用 Optional.of
方法传入一个 null
值,会抛出 NullPointerException
。如果我们不确定一个值是否为空,可以使用 Optional.ofNullable
方法,它会根据值是否为空,返回一个相应的 Optional
对象。例如:
1 | // 创建一个可能为空的 Optional 对象 |
Optional 对象的使用方法
Optional
对象提供了一些方法,让我们可以更方便地处理可能为空的值,而不需要显式地进行空值检查或者使用 null
。以下是一些常用的方法。
isPresent()
1 | Optional<Person> personOptional = daoService.selectOne(person1); |
get()
如果 Optional
对象包含一个非空的值,返回该值,否则抛出 NoSuchElementException
异常。
1 | Optional<Person> personOptional = daoService.selectOne(person1); |
ifPresent(Consumer<? super T> action)
1 | Optional<Person> personOptional = daoService.selectOne(person1); |
orElse(T other)
如果 Optional
对象包含一个非空的值,返回该值,否则返回给定的默认值。
1 | Optional<Person> personOptional = daoService.selectOne(person1); |
orElseGet(Supplier<? extends T> supplier)
如果 Optional
对象包含一个非空的值,返回该值,否则返回由给定的供应者操作生成的值。
当orElse
的参数是间接计算得来的时候,使用orElse
可能导致NullPointerException
,当orElse
的参数是间接计算得来的时候。虽然这种说法有点牵强(因为并不是orElse
导致了空指针异常),但是使用orElseGet
确实可以避免这种情况。
两者的明显(也是唯一)区别是前者需要传递的参数是一个值(通常是为空时的默认值),后者传递的是一个函数。我们看一下源代码:
1 | /** |
简单解释为,我们使用Optional
包装的变量如果不为空,返回它本身,否则返回我们传递进去的值。orElseGet
参数为Supplier
接口,它是一个函数式接口,它的形式是这样的:() -> { return computedResult }
,即入参为空,有返回值(任意类型的)。
我们可能考虑的问题是:何时使用orElse
和何时使用orElseGet
?看起来可以使用orElseGet
的时候,使用orElse
也可以代替(因为Supplier
接口没有入参),而且使用orElseGet
还需要将计算过程额外包装成一个 lambda 表达式。
一个关键的点是,使用Supplier
能够做到懒计算,即使用orElseGet
时。它的好处是,只有在需要的时候才会计算结果。具体到我们的场景,使用orElse
的时候,每次它都会执行计算结果的过程,而对于orElseGet
,只有Optional
中的值为空时,它才会计算备选结果。这样做的好处是可以避免提前计算结果的风险。
1 | Person person1 = new Person(); |
map(Function<? super T,? extends U> mapper)
如果 Optional
对象包含一个非空的值,对该值应用给定的映射函数,返回一个包含映射结果的 Optional 对象,否则返回一个空的 Optional
对象。
1 | Person person1 = new Person(); |
flatMap(Function<? super T,Optional<U>> mapper)
map和flatMap的区别:
map
会将传入的Function
函数的结果进行封装,先看源码:
1 | public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { |
flatMap
会直接返回Function
函数执行的结果,看源码:
1 | public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { |
所以在使用的时候,下面这段代码会报错。
1 | public static String getStudentName(School school){ |
而下面这段代码不会报错。
1 | public static String getStudentName(School school){ |
filter(Predicate<? super T> predicate)
如果 Optional
对象包含一个非空的值,并且该值满足给定的谓词条件,返回包含该值的 Optional
对象,否则返回一个空的 Optional
对象。
1 | Person person1 = new Person(); |