Java中的Optional

从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。
本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。

Optional

传统的判空处理

我们先看这样一个例子,例如我们使用名字去查找个人信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class User {
private String name;

public String getName() {
return name;
}

public String getFullName() {
return fullName;
}

private String fullName;

public User(String name, String fullName) {
this.name = name;
this.fullName = fullName;
}

}
1
2
3
4
5
6
7
8
9
public class UserRepository {
public User findUserByName(String name) {
if (name.equals("James")) {
return new User("James", "James Harden");
} else {
return null;
}
}
}

我们模拟一个数据库中存在的用户名

1
2
3
4
5
6
7
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("James");
System.out.println(user.getFullName());

}
// 打印结果 => James Harden

我们模拟一个数据库中没有的用户名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("James");
System.out.println(user.getFullName());

}
// 打印结果 => 报错NullPointerException,在Java中引用一个空引用的属性或者调用一个空引用的方法的时候,就会直接报NullPointerException
//为了避免报错,我们经常会在方法加上判空
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("Rose");
if (user != null) {
System.out.println(user.getFullName());
} else {
// 在查询为null的时候,我们就会自动创建一个默认的用户
User defaultUser = new User("Jim", "Jim Green");
System.out.println(defaultUser.getFullName());
}
}

在实际的代码逻辑中,会有大量的判空处理,我们采用上面的demo的话,会让我们的代码显得非常臃肿,而Optional的引入正好可以完美解决这个问题。

Optional的基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
Optional<Object> optionalBox = Optional.empty();
// 打印false
System.out.println(optionalBox.isPresent());
// 打印true
System.out.println(optionalBox.isEmpty());
}

public static void main(String[] args) {
String value = "Edison";
Optional<String> optionalBox = Optional.of(value);
// 打印true
System.out.println(optionalBox.isPresent());
// 打印false
System.out.println(optionalBox.isEmpty());
}

用of创建的对象必须包含值,你要确保传递给of方法的值部位null,否则还是会报NullPointerException。

但是在实际处理业务中,我们不确定传入的对象是否为null,这时候我们就建议使用ofNullable.

如果我们要从Optional这个盒子中取出值,我们可以使用get.

1
2
3
String value = "Edison";
Optional<String> optionalBox = Optional.ofNullable(value);
String value2 = optionalBox.get();

但是我们并不推荐使用get,这并不是Optional设计的初衷.

接下来我们使用Optional来改造下开始的例子

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
public class UserRepository {
public Optional<User> findUserByName(String name) {
if (name.equals("Edison")) {
return Optional.of(new User("James","Harden"));
} else {
return Optional.empty();
}
}
}

public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison");
User user = optionalUser.get();
System.out.println(user.getFullName());
}
}

public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison");
if (optionalUser.isPresent()) {
System.out.println(optionalUser.get().getFullName());
} else {
User defaultUser = new User("Neo", "Green");
System.out.println(defaultUser.getFullName());
}
}

截至到目前我们看到这和开始的例子本质上区别不大,代码仍不够简洁,这也不是Optional设计的初衷,接下来我们使用函数式变成改造下代码:

1
2
3
4
5
6
7
8
9
// 正确的取值方式
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison1");
User user = optionalUser.orElse(new User("Neo","Green"));
System.out.println(user.getFullName());
User user2 = optionalUser.orElseGet(()->new User("Hello","World"));
System.out.println(user2.getFullName());
}

orElse:不管传入的是空还是非空,都会执行传入的这个参数(都会去直接new User())

orElseGet:只有在为空的情况下,才会去执行。

1
2
3
4
5
6
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison1");
optionalUser.orElseThrow(() -> new RuntimeException("User not found!!!"));
}
//输出 => Exception in thread "main" java.lang.RuntimeException: User not found!!!

条件判断

1
2
3
4
5
6
7
8
9
10
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison");
optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
// 如果user存在对象,则执行语句,反之则什么也不做
optionalUser.ifPresentOrElse(
user -> System.out.println(user.getFullName()),
() -> System.out.println("User not Found!!!")
);
//user存在执行System.out.println(user.getFullName())
//不存在则执行System.out.println("User not Found!!!")

过滤方法

1
2
Optional<User> optionalUser2 = optionalUser.filter(user -> user.getFullName().equals("Edison"));
System.out.println(optionalUser2.isPresent());

值的转换

1
2
Optional<String> optionalFullName = optionalUser.map(User::getFullName);
System.out.println(optionalFullName.get());

flatMap使用

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
// 我们改造下User
public class User {
private String name;

public String getName() {
return name;
}

public Optional<String> getFullName() {
return Optional.ofNullable(fullName);
}

private String fullName;

public User(String name, String fullName) {
this.name = name;
this.fullName = fullName;
}

}

@Test
public void test_case_001(){
Optional<Optional<String>> s = optionalUser.map(User::getFullName);
Optional<String> optional = optionalUser.flatMap(User::getFullName);
}

与Stream结合

1
2
3
4
5
6
7
8
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUserByName("Edison");
Stream<String> a = optionalUser
.map(User::getFullName)
.stream();
a.forEach(System.out::println);
}

不推荐的使用场景

  • 不应该用在class的字段,会增加额外的内存消耗,也是对象的序列化变得复杂
  • 不应该用在方法的参数,会使得方法的使用和理解变得复杂
  • 不应该用在构造器参数
  • 不应该用在集合的参数类型
  • 不建议使用get方法,可能会导致NoSuchElementException出现,推荐用ifPresent,orElse或者orElseThrow等方法

Java中的Optional
https://skynetboys.github.io/2023/11/26/Java中的Optional/
Author
Edison
Posted on
November 26, 2023
Licensed under