Lambda 表达式
函数式接口
Stream 流
Optional 类使用
案例一引入
Optional 简单介绍 :
- null 值在编程中常常是令人头疼的问题,处理不好可能出现 NullPointerException 空指针异常
- 为了避免的空指针异常,代码中会充斥着繁琐的 null 检查和条件嵌套
- Optional 类可以让编程人员更加安全、优雅地处理可能为空的值,避免空指针异常
需求:通过用户名从数据库中查询对应名字的用户信息,封装成 User 类返回。
前期准备
- 定义
User.java 类,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package OptionalExample;
public class User {
String name;
String fullname;
public User(String name, String fullname) {
this.name = name;
this.fullname = fullname;
}
public String getName() {
return name;
}
public String getFullname() {
return fullname;
}
}
|
- 定义 Repository 持久层对象查找接口
findUserByName,如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
package OptionalExample;
public class UserRepository {
public User findUserByName(String name) {
if(name.equals("Albert")){
return new User("Albert", "Albert Shen");
} else {
return null;
}
}
}
|
示例一:直接输出,不检查空指针
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package OptionalExample;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("Tom");
// Exception in thread "main" java.lang.NullPointerException
// at OptionalExample.OptInJava.main
// 示例一:如果直接输出,会报空指针异常
System.out.println(user.getFullname());
}
}
|
示例二:使用条件判断空指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package OptionalExample;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("Tom");
// 示例二:条件判断空指针
if(user != null){
System.out.println(user.getFullname());
} else {
User defaultUser = new User("Neo", "Thoms Anderson");
System.out.println(defaultUser.getFullname());
}
}
}
|
Optional 引入介绍
实际项目中,可能会处理大量为空的值,这样代码会充斥着大量的条件嵌套,会让代码显得非常臃肿,难以维护,这是为什么要引入 Optional 的原因。

Optinal 就像是一个容器,或者盒子,可以包含某种类型的值,也可以什么都不包含例如:null。并且提供一系列方法来方便地操作内部的值。
同时,Optional 的设计也考虑了函数式编程的原则,可以与 Lambda 表达式 和 Stream API 等特性结合使用。

可以优雅地进行一些链式调用,不用像以往那样用命令式编程 Imperative Programming 的方式编写大量的 if 语句来检查 null 值。
Optional 基本使用
- 使用 empty() 方法创建一个空对象,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package OptionalExample;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 使用 empty 方法初始化
Optional<Object> optionalBox = Optional.empty();
// 使用 optional 内置对象的方法来检测内部值的状态
// 1. isPresent():用于检查 optional 对象中是否存在值,返回布尔值
// - 包含值,返回 true ; 不包含值,返回 false
System.out.println("isPresent : " + optionalBox.isPresent());
// 2. isEmpty():用于检查 optional 对象中是否为空,返回布尔值
// - 为空,返回 true ; 不为空,返回 false
System.out.println("empty : " + optionalBox.isEmpty());
}
}
|
- 使用 of() 方法创建一个包含值的对象,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package OptionalExample;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 创建一个包含值的 optional 对象
String value = "Albert";
Optional<String> optionalEx = Optional.of(value);
System.out.println(optionalEx.isPresent());
System.out.println(optionalEx.isEmpty());
}
}
|
注意:用 of 方法创建的对象必须包含值,所以需要编程人员确保传递给 of 方法的值不为 null,否则会抛出 NullPointerException。
应用:用于那些能够确定值不为空的场景。
- 使用 ofNullable() 方法创建一个对象,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package OptionalExample;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 创建一个包含值的 optional 对象
String value = "Albert";
// 使用 ofNullable 创建对象,传入盒子 value 既可以是有值也可以是 null
Optional<String> optionalEx = Optional.ofNullable(value);
System.out.println(optionalEx.isPresent());
System.out.println(optionalEx.isEmpty());
}
}
|
应用:不确定一个对象是否有值的时候使用。
- 使用 get() 方法取值,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package OptionalExample;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 创建一个包含值的 optional 对象
String value = "Albert";
// 使用 ofNullable 创建对象,传入盒子 value 既可以是有值也可以是 null Optional<String> optionalEx = Optional.ofNullable(value);
// 将 optional 中的值取出来,使用 get() 方法
String val = optionalEx.get();
System.out.println(val);
System.out.println(optionalEx.isPresent());
System.out.println(optionalEx.isEmpty());
}
}
|
注意:虽然使用 get() 方法取值非常简单与直观,但是一般不推荐使用这种方法。因为这不是 Optional 设计的初衷。
使用 Optional 改造案例一
- 在 UserRepository 中添加方法 findUserByNamePlus() 方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package OptionalExample;
import java.util.Optional;
public class UserRepository {
public User findUserByName(String name) {
if(name.equals("Albert")){
return new User("Albert", "Albert Shen");
} else {
return null;
}
}
public Optional<User> findUserByNamePlus(String name) {
if(name.equals("Albert")){
return Optional.of(new User("Albert", "Albert Shen"));
} else {
return Optional.empty();
}
}
}
|
- 直接使用 get() 方法获取 Optional 中值,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
User user = optionalUser.get();
System.out.println(user.getFullname());
}
}
|
由于传入方法参数找不到,所以 Optional 盒子中没有值,使用 get() 方法报错如下:
1
2
3
|
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:148)
at OptionalExample.OptInJava.main(OptInJava.java:18)
|
- 添加 isPresent() 方法判断,避免 Optional 盒子中没有值使用 get() 获取不到的问题,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
if(optionalUser.isPresent()){
System.out.println(optionalUser.get().getFullname());
} else {
User defaultUser = new User("Neo", "Thoms Anderson");
System.out.println(defaultUser.getFullname());
}
}
}
|
可以看到上边代码结构和之前的 if 条件嵌套没有本质上区别,依旧是命令式编程 imperative programming 方法在做判断和检查,这显然不是 Optional 设计的初衷和正确的使用方法。
函数式编程正确使用 Optional
- orElse() 与 orElseGet() 方法区别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
User user = optionalUser.orElse(new User("Neo", "Thoms Anderson"));
System.out.println(user.getFullname());
User user2 = optionalUser.orElseGet(() -> new User("Neo", "Thoms Anderson"));
System.out.println(user2.getFullname());
}
}
|
orElse() 和 orElseGet() 方法功能上很类似,但是执行上有所不同。
- 对于 orElse() 来说,不管是 Optional 对象为空或者非空,都会执行传入这个参数(用于设置默认值)。
- 而 orElseGet() 只有在对象为空的情况下,才会去执行
Supplier 里边这个方法。
最佳实践:当默认值已经确定,并且获取默认值代价不高的时候,可以使用 orElse() 方法;当获取默认值的代价比较高,例如:需要进行一些计算或者其他操作时,使用 orElseGet() 方法是更好的选择,因为Supplier 中的方法只有在必要的时候才会执行。
- orElseThrow() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
optionalUser.orElseThrow();
}
}
|
如果 Optional 对象为空,此时它就会抛出一个 NoSuchElementException 异常。和之前使用 get() 方法抛出异常是一样的。

orElseThrow() 方法接受一个 Supplier 的函数式接口作为参数,通过实现该接口可以生成并抛出一个自定义的异常。
1
2
3
4
5
6
7
8
9
10
11
12
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
}
}
|
- ifPresent() 与 ifPresentOrElse() :根据值的不同进行不同的操作。

该方法接收一个 Comsumer 的函数式接口作为参数,可以使用 Lambda 表达式实现这个 Consumer 函数式接口。
- 如果 Optional 类中含值,就会执行 Lambda 表达式中的方法;
- 如果 Optional 类中不含值,则什么都不做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
Optional<User> optionalUser2 = userRepository.findUserByNamePlus("Albert");
optionalUser.ifPresent(user -> System.out.println(user.getFullname()));
optionalUser2.ifPresent(user -> System.out.println(user.getFullname()));
}
}
|
如果希望值为空的时候执行其他操作,就需要使用 ifPresentOrElse() 方法, 如下:

该方法允许在 Optional 包含值的时候,执行 Comsumer 函数式编程接口 action;不包含值的时候,执行 Runnable 工作 emptyAction。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");
Optional<User> optionalUser2 = userRepository.findUserByNamePlus("Albert");
optionalUser.ifPresent(user -> System.out.println(user.getFullname()));
optionalUser2.ifPresent(user -> System.out.println(user.getFullname()));
optionalUser.ifPresentOrElse(
user -> System.out.println(user.getFullname()),
() -> System.out.println("User not found")
);
}
}
|
- filter() 方法:如果想过滤 Optional 对象中的值,根据给定的条件来决定是否保留该值。

这个方法接收一个 Predicate 的函数式接口作为参数,该接口用于定义过滤条件。如果值存在且满足条件,返回一个包含原始值的新的 Optional 对象,否则返回一个空的 Optional 对象。
下边使用 Lambda 表达式实现 Predicate 函数式接口,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");
// 若是 Predicate 函数式接口中,表达式返回的是 true,会返回一个包含值的 Optional 对象 ;
// 表达式返回 false ,返回一个空的 Optional 对象。
Optional<User> optionalUser2 = optionalUser.filter(user -> user.getFullname().equals("Albert Shen"));
System.out.println(optionalUser2.isPresent());
}
}
|
- map() :用于对 Optional 中的值进行转换,并返回一个新的 Optional 对象,其中包含了转换后的值。

这个方法接收一个 Function 函数型接口,这个函数会被应用到 Optional 中的值上,即:在 Function 中应用该值,并且返回一个新的 Optional 对象;否则,返回一个空的 Optional 对象。
代码示例,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");
// map参数:执行内部的 user 的一个 getFullname 方法,然后将执行后的结果重新包装到另外一个 Optional 对象里边
Optional<String> optionalFullName = optionalUser.map(user -> user.getFullname());
// 上述写法等价以下简化写法:方法引用
optionalUser.map(User::getFullname);
System.out.println(optionalFullName.get());
System.out.println(optionalFullName.isPresent());
}
}
|
补充:
- 理解 map() 方法的关键就是它用于 Optional 中值的变换(类似矩阵变换),这个变换基于提供给 map() 方法的函数,并且这个变换是可选的。如果 Optional 本身为空,那么不会发生变换;如果 Optional 本身不为空,则会执行方法变换。
- 此外,map() 方法不会改变原始的 Optional 对象,而是返回一个新的 Optional 对象。因此,这个方法可以链式调用,以进行多次的转换操作。
- flatMap():与 map() 方法类似,用于扁平化嵌套的 Optional 结构,以避免引入不必要的嵌套层级。具体表现为:flatMap() 方法返回的必须为另一个 Optional 对象,这意味着 flatMap() 方法可以用于嵌套的 Optional 情况,它会将两个 Optional 对象合并为一个。如果原始的 Optional 对象为空,或者转换函数返回的对象为空,最终得到的是一个空的 Optional 对象。
修改 User.java 类中 getFullName() 方法返回值,如下:
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
|
package OptionalExample;
import java.util.Optional;
public class User {
String name;
String fullname;
public User(String name, String fullname) {
this.name = name;
this.fullname = fullname;
}
public String getName() {
return name;
}
// public String getFullname() {
// return fullname;
// }
// 方法重载意味着可以在同一个类中拥有多个同名方法,只要它们的参数列表不同即可。编译器根据方法的参数来决定调用哪个方法。
// 为了使方法重载有效,以下两个条件至少有一个必须满足:
// 1. 参数的数量不同。
// 2. 参数的类型不同(Java会根据参数的类型和数量来区分方法)。
// 如果两个 getFullName 方法的参数列表相同,但返回类型不同,Java编译器会报错,因为仅根据返回类型无法区分这两个方法。
public Optional<String> getFullName() {
return Optional.ofNullable(fullname);
}
}
|
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package OptionalExample;
import java.util.Optional;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");
Optional<Optional<String>> s = optionalUser.map(User::getFullName);
Optional<String> optional = optionalUser.flatMap(User::getFullName);
}
}
|
可以发现,使用 map() 方法和使用 flatMap() 方法返回值有所区别。通常情况下,如果只需要对 Optional 的值进行转换,不在乎嵌套 Optional ,那么使用 map() 方法比较合适;如果需要进行一些额外的操作,需要返回另外的 Optional 对象,此时为了简化链式调用处理参数,可以使用 flatMap() 方法来扁平化嵌套 Optional 对象。
- stream() :将一个 Optional 对象转换为一个 Steam 流。
Optional 中提供了Stream 方法,可以将一个 Optional 对象转化为一个 Stream 对象,然后对其中的值进行流操作。如果 Optional 对象包含值,那么将这个值封装到一个 Stream 流对象中;如果 Optional 对象为空,那么将创建一个空的 Stream 流。
代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package OptionalExample;
import java.util.Optional;
import java.util.stream.Stream;
public class OptInJava {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");
Stream<String> a = optionalUser
.map(User::getName)
.stream();
a.forEach(System.out::println);
}
}
|
小结
Optional 推荐使用场景
Optional 普遍用于方法的返回类型,表示方法可能不返回结果。当有一个方法可能返回一个值,或者什么都不返回的时候(null),可以使用 Optional 封装该返回值。

当设计一个 API 时,可以引导 API 的使用者,告诉他们这个结果可能不存在,并强制调用者处理这种可能性,可以减少错误的发生。
Optional 不推荐使用的场景
- 不推荐用于类的字段。
1
2
3
|
public class User {
Optional<String> name;
}
|
注:Optional 创建和管理有一定开销,并不适合用作类字段。使用 Optional 作为字段类型会增加内存消耗,并且会使得对象的序列化变得更加复杂。
- 不推荐用于方法的参数。
1
2
3
4
5
6
|
public class User {
public void updateUser(Optional<String> name) {
// ...
}
}
|
注:将 Optional 对象作为方法的参数时,会使得方法的使用和理解变得复杂。如果希望方法接收一个可能为空的值,通常有更好设计选择,比如:方法重载等。
正确实例代码如下:
1
2
3
4
5
6
7
8
9
|
public class User {
public void updateUser(String name) {
// ...
}
public void updateUser() {
// ...
}
}
|
- 不推荐用于构造函数参数。
1
2
3
4
5
6
|
public class User {
public User(Optional<String> name) {
// ...
}
}
|
注:类似于方法参数,Optional 不应该用于构造器参数,这种做法会迫使调用者创建 Optional 实例。应该通过构造器重载来解决。
正确实例代码如下:
1
2
3
4
5
6
7
8
9
|
public class User {
public User(String name) {
// ...
}
public User() {
// ...
}
}
|
- 不推荐作为集合参数类型。
1
2
3
|
public Optional<List<User>> getUsers() {
return Optional.ofNullable(getList());
}
|
注:如果一个方法返回一个集合,并且这个集合可能为空,不应用使用 Optional 来包装它。集合已经可以很好处理空集合的情况,没必要使用 Optional 包装集合。
正确实例代码如下:
1
2
3
4
5
6
7
|
public List<User> getUsers() {
if(getList() == null) {
return Collections.emptyList();
} else {
return getList();
}
}
|
- 不推荐使用 get() 方法。
1
2
3
4
5
6
|
String value = optionalValue.get();
// 或者
if (optionaValue.isPresent()){
String value = optionalValue.get();
}
|
注:调用 Optional 的 get() 方法前,没有确认值是否存在,可能会导致 NoSuchElementException 异常抛出。即使使用 ifPresent() 和 get() 的组合也不是最好的选择,这种操作和直接调用可能为 null 的引用没有多大区别,仍然需要进行显示的检查,以避免异常。
应当使用 ifPresentOrElse() 或者 orElse() 或者 orElseThrow() 等方法,正确实例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
optionalValue.ifPresent(System.out::println);
optionalValue.ifPresentOrElse(
System.out::println,
() -> System.out.println("empty")
);
String value = optionalValue.orElse("default");
String value = optionalValue.orElseGet(() -> "default");
String value = optionalValue.orElseThrow();
|
附录
参考文献
版权信息
本文原载于kitebin.top,遵循CC BY-NC-SA 4.0协议,复制请保留原文出处。