文章目录
Why Optional
需求
从数据库中根据Name查询对应用户
正常流程
-
定义一个
User
类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; } }
-
定义一个
Respository
类,模仿数据库查询public class UserRepository { public User findUserByName(String name){ // 模仿一下从数据库中查询的逻辑 if (name.equals("Hansdas")){ return new User("Hansdas","Hansdas Chen"); }else { // 查不到则返回null return null; } } }
-
在main中尝试查询
public class Main { public static void main(String[] args) { UserRepository userRepository = new UserRepository(); User user = userRepository.findUserByName("Hansdas"); System.out.println(user.getFullName()); } }
输出如下,查询成功
Hansdas Chen
空指针异常NullPointerException
但此时如果查询一个不存在的name,比如"Hansbas",findUserByName
返回一个null对象,调用null的getFullName()
时,则会出现空指针异常NullPointerException
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "User.getFullName()" because "user" is null
at Main.main(Main.java:6)
NullPointerException
:Java中尝试访问一个空引用的属性,或调用一个空引用的方法的时候,就会抛出空指针异常
处理空指针异常
一般最容易想到的就是用if (xx != null){...}
进行显式处理,比如:
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
User user = userRepository.findUserByName("Hansdas2");
if (user != null){
System.out.println(user.getFullName());
}
}
缺点
实际项目中需要处理大量可能为null的值,如果都用if (xx != null){...}
处理,会使得代码很难臃肿,难以维护
因此需要Optional
What’s Optional
Optional 类是一个可以为null的容器对象,提供了很多方法,帮助解决空指针异常问题,不用到处显式地用if (xx != null){...}
做空指针检测。
这里的重点其实就两个:
- Optional是一个容器,用来装某种类型(
T
)的值,也可以是null - Optional可以用来处理空指针异常
Optional 基本用法
创建空的Optional对象
-
获取Optional对象
Optional<Object> optionalBox = Optional.empty(); // 创建一个空的Optional对象
检测Optional对象内部值的状态 :isPresent()
&isEmpty()
System.out.println(optionalBox.isPresent()); // 检测Optional对象是否存在值,存在返回true
System.out.println(optionalBox.isEmpty()); // 与isPresent相反,不存在返回true
false
true
将值放入Optional: of()
&ofNullable()
-
of()
:用于已知非null的情况String name = "Hansdas"; Optional<String> optionalBox = Optional.of(name); System.out.println(optionalBox.isPresent()); System.out.println(optionalBox.isEmpty());
true false
-
如果传入对象为null,会抛出空指针异常
String name = null; Optional<String> optionalBox = Optional.of(name); System.out.println(optionalBox.isPresent()); System.out.println(optionalBox.isEmpty());
-
-
ofNullable()
:用于不知道处理对象是否为null的情况String name = null; Optional<String> optionalBox = Optional.ofNullable(name); System.out.println(optionalBox.isPresent()); System.out.println(optionalBox.isEmpty());
false true
将值取出 get()
get()
:很直观,但不建议
String getName = optionalBox.get();
常用方法
方法 | 描述 |
---|---|
static <T> Optional<T> empty() | 返回空的 Optional 实例。 |
boolean equals(Object obj) | 判断其他对象是否等于 Optional。 |
Optional<T> filter(Predicate<? super <T> predicate) | 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。 |
Optional flatMap(Function<? super T,Optional> mapper) | 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional |
T get() | 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
int hashCode() | 返回存在值的哈希码,如果值不存在 返回 0。 |
void ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
boolean isPresent() | 如果值存在则方法会返回true,否则返回 false。 |
Optional<U> map(Function<? super T,? extends U> mapper) | 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。 |
static <T> Optional<T> of(T value) | 返回一个指定非null值的Optional。 |
static <T> Optional<T> ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。 |
T orElse(T other) | 如果存在该值,返回值, 否则返回 other。 |
T orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 |
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
String toString() | 返回一个Optional的非空字符串,用来调试 |
Optinal实现需求
现在用Optional去修改UserRepository
,来解决查询时可能的空指针异常:
-
修改
UserRepository
public class UserRepository { public Optional<User> findUserByName(String name){ // 模仿一下从数据库中查询的逻辑 if (name.equals("Hansdas")){ return Optional.of(new User("Hansdas","Hansdas Chen")); }else { // 查不到则返回null return Optional.empty(); } } }
为什么不建议使用.get()
-
在main中获取一下
public class Main { public static void main(String[] args) { UserRepository userRepository = new UserRepository(); Optional<User> optionalUser = userRepository.findUserByName("Hansdas"); User user = optionalUser.get(); System.out.println(user.getFullName()); } }
- 如果查询的name不存在,
get()
会抛出异常,但是NoSuchElementException
,而不是user.getFullName()
抛出的NullPointerException
- 如果查询的name不存在,
-
很自然地就想到,加个if判断下!
public class Main { public static void main(String[] args) { UserRepository userRepository = new UserRepository(); Optional<User> optionalUser = userRepository.findUserByName("Hansdas2"); if (optionalUser.isPresent()){ // Optional容器不为空 User user = optionalUser.get(); System.out.println(user.getFullName()); }else{ User defaultUser = new User("Default", "Default Name"); System.out.println(defaultUser.getFullName()); } } }
这个时候的代码又跟引入Optional之前的代码没啥区别了,依旧用命令式编程(imperative programming)的方式做判断和检查,并没有实现Optional ”减少臃肿,便于维护“的初衷
应当使用函数式编程的方式,来完成Optional的正确使用
Optional 常用的操作方式
下面详细说说Optional一些常用的操作(如何实现”减少臃肿,便于维护“的初衷)
获取值
-
orElse()
获取值,如果容器为空则返回默认值UserRepository userRepository = new UserRepository(); Optional<User> optionalUser = userRepository.findUserByName("Hansdas2"); User user = optionalUser.orElse(new User("Default", "Default Name")); System.out.println(user.getFullName());
-
orElseGet()
:使用lambda表达式实现Supplier接口,Optional容器为空执行User user1 = optionalUser.orElseGet(() -> new User("Default", "Default Name")); System.out.println(user1.getFullName());
-
orElse()
与orElseGet()
的区别:orElse()
不论Optional对象空或非空,都会执行传入参数new User("Default", "Default Name")
orElseGet()
仅当Optional对象为空式,执行传入参数new User("Default", "Default Name")
- 当默认值确定,且获取默认值的代价不会很高时可以使用
orElse()
- 如果获取默认值代价比较高(比如需要进行一些计算),建议用
orElseGet()
-
orElseThrow()
:默认跟get一样,为空时抛出NoSuchElementException
,也可以实现Supplier接口自定义抛出异常optionalUser.orElseThrow(); optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
根据值的不同状态进行操作
-
ifPresent()
实现cosumer函数式接口,如果Optional含值,执行lambda里的方法,不含值则不操作UserRepository userRepository = new UserRepository(); Optional<User> optionalUser = userRepository.findUserByName("Hansdas"); optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
-
``ifPresentOrElse()`:用两个lambda表达式实现Consumer和Runnable函数式接口
- Optional包含值的时候执行一个操作(Consumer),不包含值的时候执行另一个操作(Runnable)
Optional<User> optionalUser = userRepository.findUserByName("Hansdas2"); optionalUser.ifPresentOrElse( user -> System.out.println(user.getFullName()), () -> System.out.println("User Not Found") );
-
filter()
:根据给定的条件来决定是都保留该值Optional<User> optionalUser1 = optionalUser.filter( user -> user.getFullName().equals("Hansdas Chen") ); System.out.println(optionalUser1.isPresent());
-
map()
:对Optional中的值进行转换- 不会改变原始的Optional对象,而是返回一个新的Optional对象(包含转换后的值 )。
- 如果Optional是空的,直接返回一个空的Optional对象
// User类型的Optional -> String类型的Optional Optional<String> optionalFullName = optionalUser.map(user -> user.getFullName()); // 可以用method reference简化lambda表达式 Optional<String> optionalFullName = optionalUser.map(User::getFullName);
-
flatMap()
:可以用于嵌套的Optional情况:-
如果
getFullName
返回的是一个Optional对象public class User { ... public Optional<String> getFullName() { return Optional.ofNullable(fullName); } }
-
main中使用flatMap与map
// map会有嵌套 Optional<Optional<String>> optionalFullName = optionalUser.map(User::getFullName); // flatMap没有嵌套 Optional<String> optionalFullName2 = optionalUser.flatMap(User::getFullName);
-
-
stream()
:可以将Optional对象转化为一个Stream对象,对其中的值进行流操作- 如果Optional对象包含值,则将其封装到一个Stream流中
- 如果Optional对象为空,则返回一个空的Stream
Stream<String> a = optionalUser
.map(User::getName)
.stream(); // 返回一个String流
a.forEach(System.out::println);
总结
Optional普遍用于方法的返回类型,表示方法可能不返回结果(即返回null)
Optional不适用的场景
-
不适合用于类的字段:因为Optional的管理有一定的额外开销,作为字段类型会增加内存消耗,并且会使得对象的序列化变得复杂
// 不建议 public class User{ Optional<String> name; // ... }
-
不适合用于方法的参数:会使方法的使用和理解变得复杂
// 不建议 public class User{ public void updateUser(Optional<String> name){ // ... } // ... }
-
如果希望函数参数接受以一个可能为空的值,可以通过方法重载等方式实现
public class User{ public void updateUser(){ // ... } public void updateUser(Optional<String> name){ // ... } // ... }
-
-
不适合用于构造器参数:会迫使调用者创造Optional实例,增加开销
// 不建议 public class User{ public User(Optional<String> name){ // ... } }
-
如果希望构造器参数接受以一个可能为空的值,也可以通过方法重载等方式实现
public class User{ public User(){ // ... } public User(Optional<String> name){ // ... } }
-
-
不适合用作集合的参数类型:集合本身就可以处理空集合的情况,没必要用Optional包装
// 不建议 Optional<List<User>> ...
Collection.emptyList()
-
不建议使用
get()
方法:无法确认是否有值,空的Optional会抛出NoSuchElementException
。如果用isPresent()
先检查Optional是否为空,再get()
,跟直接检查对象是否为null没什么区别- 可以使用
ifPresent
,OrElse
,orElseThrow
等方法
- 可以使用