NPE问题就是,我们在开发中经常碰到的NullPointerException
场景
user.getAddress().getProvince();
这种写法,在user为null时,是会报NullPointerException异常的。为了解决这个问题,于是采用下面的写法
if(user!=null){
Address address = user.getAddress();
if(address!=null){
String province = address.getProvince();
}
}
这种写法增加了很多if-else代码块,有什么更优雅的写法呢。JAVA8提供了Optional类来优化这种写法
Optional
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
Optional的构造方法
Optional(T value)
源码:
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
Optional(T value)
,即构造函数,它是private权限的,不能由外部调用的。
1. Optional.of(T value)
源码:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
也就是说of(T value)函数内部调用了构造函数。根据构造函数的源码我们可以得出两个结论:
- 通过of(T value)函数所构造出的Optional对象,当value值为空时,依然会报NullPointerException。
- 通过of(T value)函数所构造出的Optional对象,当value值不为空时,能正常构造Optional对象。
2. Optional.empty()
源码:
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
empty() 的作用就是返回EMPTY对象,用来构造一个空的 Optional,即该 Optional 中不包含值
3. Optional.ofNullable(T value)
源码:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
相比较Optional.of(T value)的区别就是,当value值为null时,of(T value)会报NullPointerException异常;ofNullable(T value)不会throw Exception,ofNullable(T value)直接返回一个EMPTY对象。
由此可见,实际项目运用中Optional.ofNullable(T value)的使用场景要远远多余Optional.of(T value)。当你不想隐藏NullPointerException,而是要立即报告,这种情况下就用Of函数。
Optional其他相关方法
4. orElse(T other),orElseGet(Supplier other)和orElseThrow(Supplier exceptionSupplier)
这三个函数放一组,都是在上面构造函数传入的value值为null时,进行调用的。
orElse(T other),orElseGet(Supplier other)
orElse和orElseGet的用法如下所示,相当于value值为null时,给予一个默认值:
@Test
public void test() {
User user = null;
user = Optional.ofNullable(user).orElse(createUser());
user = Optional.ofNullable(user).orElseGet(() -> createUser());
}
public User createUser(){
User user = new User();
user.setName("zhangsan");
return user;
}
这个两个函数简单解释为,我们使用Optional包装的变量如果不为空,返回它本身,否则返回我们传递进去的值。orElseGet参数为Supplier接口,它是一个函数式接口,它的形式是这样的:() -> { return computedResult },即入参为空,有返回值(任意类型的)。
这两个函数的区别:
- 当user值为null时,无论orElse还是orElseGet都会执行createUser()方法;
- 当user值不为null时,orElse会执行createUser()方法,而orElseGet不会执行createUser()方法。
乍一看确实有点懵,明明有值,为什么还执行,怎么都觉得跟orElse的语义违背
源码:
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
两者的明显(也是唯一)区别是前者需要传递的参数是一个值(通常是为空时的默认值),后者传递的是一个函数。
其实不管是orElse还是orElseGet都会进到对应的方法里面的,所以在要执行orElse之前,那参数的值总是要知道的,所以肯定先要执行传入的方法。
简而言之:前者立即计算,后者是延迟计算
何时使用orElse和何时使用orElseGet?
看起来可以使用orElseGet的时候,使用orElse也可以代替(因为Supplier接口没有入参),而且使用orElseGet还需要将计算过程额外包装成一个 lambda 表达式。
一个关键的点是,使用Supplier能够做到懒计算。即 使用orElseGet时,它的好处是,只有在需要的时候才会计算结果。使用orElse的时候,每次它都会执行计算结果的过程,而对于orElseGet,只有Optional中的值为空时,它才会计算备选结果。这样做的好处是可以避免提前计算结果的风险。
场景举例:
class User {
// 中文名
private String chineseName;
// 英文名
private EnglishName englishName;
}
class EnglishName {
// 全名
private String fullName;
// 简写
private String shortName;
}
假如我们现在有User类,用户注册账号时,需要提供自己的中文名或英文名,或都提供,我们抽象出一个EnglishName类,它包含英文名的全名和简写(因为有的英文名确实太长了)。现在,我们希望有一个User#getName()方法,它可以像下面这样实现:
class User {
// 中文名
private String chineseName;
// 英文名
private EnglishName englishName;
public String getName1() {
return Optional.ofNullable(chineseName)
.orElse(englishName.getShortName());
}
public String getName2() {
return Optional.ofNullable(chineseName)
.orElseGet(() -> englishName.getShortName());
}
}
可以看出getName1()方法有什么风险了吗?
当用户只提供了中文名时,此时englishName属性是null,但是在orElse中,englishName.getShortName()总是会执行,这时候就会出现空指针异常。而在getName2()中,这个风险却没有。
orElseThrow
源码:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
它会在对象为空的时候抛出异常,而不是返回备选的值:
User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出NullPointerException。
5. map()和flatMap()
源码:
public final class Optional<T> {
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
}
map
如果当前 Optional 为 Optional.empty,则依旧返回 Optional.empty;否则返回一个新的 Optional,该 Optional 包含的是:函数 mapper 在以 value 作为输入时的输出值。
Optional<String> username = Optional
.ofNullable(user)
.map(user -> user.getUsername());
System.out.println("Username is: " + username.orElse("Unknown"));
而且我们可以多次使用map操作:
Optional<String> username = Optional
.ofNullable(user)
.map(user -> user.getUsername())
.map(name -> name.toLowerCase())
.map(name -> name.replace('_', ' '));
System.out.println("Username is: " + username.orElse("Unknown"));
flatMap
flatMap 方法与 map 方法的区别在于,map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional。
Optional<String> username = Optional
.ofNullable(user)
.flatMap(user -> Optional.of(user.getUsername()))
.flatMap(name -> Optional.of(name.toLowerCase()));
System.out.println("Username is: " + username.orElse("Unknown"));
6. isPresent()和ifPresent(Consumer consumer)
isPresent即判断value值是否为空,返回boolean,而ifPresent就是在value值不为空时,做一些操作。这两个函数的源码如下:
public final class Optional<T> {
//省略....
public boolean isPresent() {
return value != null;
}
//省略...
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
}
如果需要判断value不为空之后还需要do something,不要这样写:
Optional<User> user = Optional.ofNullable(user);
if (user.isPresent()){
// TODO: do something
}
可以使用ifPresent函数处理
Optional<User> user = Optional.ofNullable(user);
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
7. filter(Predicate predicate)
源码:
public final class Optional<T> {
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
}
filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。
用法:
Optional<User> user1 = Optional.ofNullable(user).filter(u -> u.getName().length()<6);
如上所示,如果user的name的长度是小于6的,则返回。如果是大于6的,则返回一个EMPTY对象。
Java9 对Optional的增强
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()
使用示例:
public class Tester {
public static void main(String[] args) {
Optional<Integer> optional=Optional.of(1);
optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() ->
System.out.println("Not Present."));
optional=Optional.empty();
optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() ->
System.out.println("Not Present."));
}
}
//输出
Value: 1
Not Present.
or(Supplier<? extends Optional<? extends T>> supplier)
有时当Optional为空时,我们想执行一些其他逻辑并也返回Optional。在Java9之前Optional类仅有orElse()和orElseGet()方法,但两者都返回非包装值。
Java9引入or()方法当Optional为空时返回另一个Optional。如果Optional有定义值,则传入or方法的lambda不被执行:
String expected = "properValue";
Optional<String> value = Optional.of(expected);
Optional<String> defaultValue = Optional.of("default");
Optional<String> result = value.or(() -> defaultValue);
Optional<String> value1 = Optional.empty();
Optional<String> result = value1.or(() -> defaultValue);
stream()
stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream;否则返回一个空的 Stream(Stream.empty())。
举个例子,在 Java8,我们会写下面的代码:
// 此处 getUserById 返回的是 Optional<User>
public List<User> getUsers(Collection<Integer> userIds) {
return userIds.stream()
.map(this::getUserById) // 获得 Stream<Optional<User>>
.filter(Optional::isPresent)// 去掉不包含值的 Optional
.map(Optional::get)
.collect(Collectors.toList());
}
而有了 Optional.stream(),我们就可以将其简化为:
public List<User> getUsers(Collection<Integer> userIds) {
return userIds.stream()
.map(this::getUserById) // 获得 Stream<Optional<User>>
.flatMap(Optional::stream) // Stream 的 flatMap 方法将多个流合成一个流
.collect(Collectors.toList());
}
标题:Java8如何处理NPE问题-Optional
作者:barryzpc
地址:https://myblog.zhengpc.com/articles/2023/09/19/1695088524727.html
说明:转载请注明出处
赞赏:如果对你有帮助,可略微支持一下