浩哥笔记

我们的征途是星辰大海

  menu
40 文章
3364 浏览
1 当前访客
ღゝ◡╹)ノ❤️

Java8如何处理NPE问题-Optional

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
说明:转载请注明出处
赞赏:如果对你有帮助,可略微支持一下
赞赏码