参考视频(B 站 UP 主:AlbertShen
):https://www.bilibili.com/video/BV1dc411X7nW/?spm_id_from=333.788.top_right_bar_window_history.content.click&vd_source=d55a4a2d31b98315ece09779a92b618b
null
值在编程中常常是一个令人头疼的问题,处理不好,就会导致 NullPointerException
空指针异常,为了避免空指针异常,代码中就会充斥着繁琐的 null
检查和条件嵌套,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;
}
}
public class UserRepository {
public User findUserByName(String name) {
if (name.equals("tkzc")) {
return new User("tkzc", "tkzc00");
} else {
return null;
}
}
}
接下类在 main
方法中进行调用,模拟从数据库中查询数据:
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("tkzc2");
System.out.println(user.getFullName());
}
}
此时会报空指针异常,如果要避免空指针异常,在代码中就需要进行一些判断:
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUser("tkzc2");
if (user != null) {
System.out.println(user.getFullName());
} else {
User defaultUser = new User("Neo", "Thomas Neo");
System.out.println(defaultUser.getFullName());
}
}
}
然而在实际的项目中,我们会处理大量可能为空的值,这样一来,代码中就会充斥着条件判断,会让代码显得非常臃肿,难以阅读和维护,这就是为什么要引入 Optional
的原因。
Optional
就像是一个容器,或者盒子,它可以包含某种类型的值,也可以什么都不包含,比如 null
,并且提供一系列方法,来方便的操作内部的值,同时 Optional
的设计,也考虑了函数式编程的原则,可以与 Lambda 表达式和 Stream API 等特性结合使用,你可以优雅的进行一些链式调用,而不用像以往那样,用命令式编程(Imperative Programing)的方式,编写大量的 if
语句来检查 null
值。
首先,我们来看一下 Optional
的基本使用方法:
首先创建一个包含空值的 Optional
对象:
Optional<Object> optionalBox = Optional.empty();
接下来,用 Optional
的内部方法来检测内部值的状态:
方法 | 说明 |
---|---|
isPresent() | 检查 Optional 中是否存在值,存在返回 true ,否则返回 false |
isEmpty() | 检查 Optional 是否为空,为空返回 true ,否则返回 false |
of(T value) | 通过 value 来创建一个包含 value 的 Optional 对象,value 不能为空 |
ofNullable(T value) | 通过 value 来创建一个包含 value 的 Optional 对象,value 可以为空 |
get() | 获取 Optional 中存储的值 |
String value = "tkzc";
Optional<String> optionalBox = Optional.of(value);
System.out.println(optionalBox.isPresent()); // true
System.out.println(optionalBox.isEmpty()); // false
用 of()
方法创建的 Optional
对象必须包含值,这就意味着传递给 of()
方法的值不能为 null
,否则会报空指针异常,主要用于确定值不为空的场景。
对于不确定是否为空的值,可以使用 ofNullable()
方法来创建 Optional
对象,此时传入的对象可以有值,也可以为空。
String value = "tkzc";
Optional<String> optionalBox = Optional.ofNullable(value);
String value2 = optionalBox.get();
System.out.println(value2); // tkzc00
虽然用 get()
方法取值非常简单,但并不推荐这样做,因为这并不是 Optional
设计的初衷。
接下来,我们用 Optional
来改造 UserRepository
中的 findUserByName
方法:
public class UserRepository {
public Optional<User> findUserByName(String name) {
if (name.equals("tkzc")) {
return new Optional.of(new User("tkzc", "tkzc00"));
} else {
return Optional.empty();
}
}
}
下面是改造 main
函数:
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
User user = optionalUser.get();
System.out.println(user.getFullName());
}
}
此时会报 NoSuchElementException
异常,因为此时 Optional
中装的是空值,所以并不能取出 User
对象,更不能获得 getFullName()
方法。此时,就需要进行判断:
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
if (optionalUser.isPresent()) {
User user = optionalUser.get();
System.out.println(user.getFullName());
} else {
User defaultUser = new User("Neo", "Thomas Neo");
System.out.println(defaultUser.getFullName());
}
}
}
此时会发现,当前的代码结构和之前的并没有本质上的区别,依旧是用命令时编程(Imperative Programing)的方式在做判断和检查,这显然不是 Optional
设计的初衷和正确的使用方法,我们应该用函数式编程的方式来操作 Optional
。
方法 | 说明 |
---|---|
orElse(T other) | 当 Optional 中有值时则返回 Optional 中的值,否则返回 other |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
User user = optionalUser.orElse(new User("Neo", "Thomas Neo"));
System.out.println(user.getFullName()); // Thomas Neo
}
}
方法 | 说明 |
---|---|
orElseGet(Supplier<? extends T> supplier) | 和 orElse() 的作用一致,只是使用 Supplier 来返回一个默认值 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
User user = optionalUser.orElseGet(() -> new User("Neo", "Thomas Neo"));
System.out.println(user.getFullName()); // Thomas Neo
}
}
orElse()
和 orElseGet()
功能上很相似,但执行方式上有所不同,对 orElse()
来说,不管 Optional
对象是否为空,都会执行传入的参数,也就是无论 optionalUser
是否为空,都会 new 一个默认的 User
对象,而 orElseGet()
只有在 Optional
对象为空的时候,才会去执行传入的 Supplier
方法,也就是只有当 optionalUser
内部值为空时,才会去 new 一个默认的 User
对象。
当默认值已经确定,并且获取默认值的代价不是很高的时候,可以使用 orElse()
;当获取默认值的代价比较高,或者获取默认值需要经过一些计算的时候,使用 orElseGet()
是更好的选择,因为它只有在必要的时候才会执行。
方法 | 说明 |
---|---|
orElseThrow() | 如果 Optional 对象为空,就会抛出一个 NoSuchElementException 的异常 |
orElseThrow(Supplier<? extends RuntimeException> supplier) | 如果 Optional 对象为空,就会抛出一个指定的异常 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
optionalUser.orElseThrow(() -> new RuntimeException("用户不存在"));
}
}
方法 | 说明 |
---|---|
ifPresent(Consumer<? super T> action) | 当 Optional 中有值时执行 action 操作,没有值则什么都不做 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
}
}
方法 | 说明 |
---|---|
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) | 当 Optional 中有值时执行 action 操作,没有值则执行 emptyAction 操作 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
optionalUser.ifPresentOrElse(
user -> System.out.println(user.getFullName()),
() -> System.out.println("用户不存在")
);
}
}
方法 | 说明 |
---|---|
filter(Predicate<? super T> predicate) | 根据给定的条件过滤值,如果值存在并且满足条件,返回包含该值的新 Optional 对象;否则,返回一个空的 Optional 对象 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc2");
Optional<User> optioanlUser2 = optionalUser.filter(
user -> user.getFullName().equals("tkzc00')
);
System.out.println(optionalUser2.siPresent()); // false
}
}
方法 | 说明 |
---|---|
map(Function<? super T, ? extends U> mapper) | 对 Optional 中的值进行转换,并返回一个新的 Optional 对象,其中包含了转换后的值;如果 Optioanl 是空的,则什么也不做,直接返回一个空的 Optional 对象 |
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc");
Optional<String> optionalFullName = optionalUser.map(User::getFullName());
System.out.println(optionalFullName.get()); // tkzc00
}
}
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc1");
Optional<String> optionalFullName = optionalUser.map(User::getFullName());
System.out.println(optionalFullName.isPresent()); // false
}
}
方法 | 说明 |
---|---|
flatMap(Function<? super T, ? extends U> mapper) | 扁平化嵌套的 Optional 结构 |
flatMap()
方法会将两个 Optional
对象合并成一个,如果原始的 Optional
对象为空,或者转换函数返回的 Optional
对象为空,那么最终得到的也是一个空的 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 Optional<String> getFullName() {
return Optional.ofNullable(fullName);
}
}
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc1");
Optional<Optional<String>> optionalFullName = optionalUser.map(User::getFullName());
}
}
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc1");
Optional<String> optionalFullName = optionalUser.flatMap(User::getFullName());
}
}
通常情况下,如果你只需要对 Optional
对象中的值进行转换,而不需要嵌套的 Optional
,那么使用 map()
方法是更合适的选择;但如果你需要进行一些操作,可能会返回另外一个 Optional
对象,那么 flatMap()
方法更适合。
方法 | 说明 |
---|---|
stream() | 将 Optional 对象转换为 Stream 对象 |
如果 Optional
对象包含值,那么就将这个值封装到一个 Stream
流中;如果 Optional
对象为空,那么将创建一个空的 Stream
流。
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
Optional<User> optionalUser = userRepository.findUser("tkzc");
Stream<String> stream = optionalUser
.map(User::getName())
.stream();
stream.forEach(System.out::println); // tkzc
}
}
总的来所,Optional
普遍用于方法的返回类型,表示方法可能不返回结果,当你有一个方法可能返回一个值,或者什么都不返回,即返回 null
时,你可以使用 Optional
。比方说当你设计一个 API 时,它能引导 API 的使用者,告诉他们这个结果可能不存在,并强制调用者处理这种可能性,可以减少错误的发生。
尽管 Optional
很有用,但也有一些不推荐的使用场景,比方说不应该用于类的字段,由于 Optional
对象的创建和管理有一定的开销,并不适合用作类的字段,使用 Optional
作为字段类型会增加内存消耗,并且会使得对象的序列化变得复杂;也不应该用作方法的参数,将 Optional
用作方法参数,会使方法的使用和理解变得复杂,如果你希望方法接收一个可能为空的值,通常有更好的设计选择,比如方法重载;不应该用于构造函数参数,类似与方法参数,Optional
也不应该用于构造器参数,这种做法,还会迫使调用者创建 Optional
实例,应该通过构造器重载来解决;不应该用作集合的参数类型,如果你的方法返回一个集合,而且这个集合可能为空,不应该用 Optional
来包装它,集合已经可以很好地处理空集合的情况,没必要用 Optional
包装集合;不建议使用 get()
方法,调用 Optional
的 get()
方法前,没有确认值是否存在,可能会导致 NoSuchElementException
,即使使用 isPresent()
和 get()
的组合也不是一个最好的选择,这样做其实和直接调用可能为 null
的引用没多大区别,因为你仍然需要进行显式的检查,以避免异常,应当使用 ifPresent()
、orElse()
或者 orElseThrow()
等方法。