Optional类的使用避免空指针

一、Optional 介绍

Optional 被定义为一个简单的容器,它可以保存类型 T 的值,其值可能是 null 或者不是 null。在 Java 8 之前一般某个函数应该返回非空对象但是偶尔却可能返回了 null,而在 Java 8 以后,不推荐你返回 null 而是返回 Optional。

二、Optional 用法

从方法作用来划分,主要可以分为:构建类方法,获取类方法,判断类方法,过滤类方法,映射类方法

1.构建类方法

  • Optional.of(T t) : 创建一个 Optional 实例,t 必须非空;
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):t 可以为 null

2.获取类方法

  • T get(): 如果调用对象包含值不为 null,返回该值,否则抛异常
  • T orElse(T other) :如果有值则将其返回,否则返回指定的 other 对象。
  • T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。
  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由 Supplier

3.判断类方法

  • boolean isPresent() : 判断是否包含对象
  • void ifPresent(Consumer<? super T> consumer) :如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。
  • void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) :如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它,否则执行 Runnable 接口的实现代码。

4.过滤类方法

  • Optional<T> filter(Predicate<? super <T> predicate):如果值存在,并且这个值匹配给定的 predicate,返回一个 Optional 用以描述这个值,否则返回一个空的 Optional。

5.映射类方法

Optional map(Function<? super T,? extends U> mapper):如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的 Optional 作为 map 方法返回值,否则返回空 Optional。

Optional flatMap(Function<? super T, Optional> mapper):如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象

三、源码解析及示例

示例代码

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Trade {
    String iName;
    String iCode;
    String iType;
}

1、创建 Optional 对象

①.Optional.of(T t)

/**
 * 有参构造
   使用描述的值构造一个实例。
   参数:值–要描述的非null值
   抛出:NullPointerException如果值为null
 */
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}
//Objects.java
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

/**
  返回一个Optional描述给定的非null值。
  参数:value 要描述的值,必须为非null
  类型参数:<T> –值的类型
  返回值:存在值的Optional,若为null则抛出NullPointerException异常
 */
public static <T> Optional<T> of(T value) {
    return new Optional<> (value);
}

示例

  public void ofTest1(){
        Trade trade = Trade.builder ()
                .iCode ("qyz0711")
                .iName ("青衣醉0711")
                .iType ("0711")
                .build ();
        Optional<Trade> optionalTrade = Optional.of (trade);
    }

②.Optional.empty()

private static final Optional<?> EMPTY = new Optional<>();

 private Optional() {
        this.value = null;
    }
/**
        返回一个空的Optional实例。 此Optional没有值。
        类型参数:<T> 不存在的值的类型
        返回值:一个空的Optional
        api注意:
        尽管这样做可能很诱人,但应通过将==与Optional.empty()返回的实例进行比较来避免测试对象是否为空。
        不能保证它是一个单例。 而是使用isPresent() 
     */
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

③.Optional.ofNullable(T value)

/**
  返回一个描述给定值的Optional ,如果为null ,则返回一个空的Optional,否则返回给定值的Optional 。
  参数:值描述的可能为null值
  类型参数:<T> –值的类型
  返回值:一个Optional与如果指定值是非当前值null ,否则一个空Optional
 */
public static <T> java.util.Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

④.示例测试

// 构建一个value不可以为null的optional对象,如果of()的入参为null会报空指针异常;
Optional<Trade> of1 = Optional.of (trade);
System.out.println ("of1:"+of1);

//返回一个描述给定值的Optional ,如果为null ,则返回一个空的Optional,否则返回给定值的Optional 。
Optional<Trade> ofNullable1 = Optional.ofNullable (trade);
Optional<Trade> ofNullable2 = Optional.ofNullable (null);
System.out.println ("ofNullable1:"+ofNullable1);
System.out.println ("ofNullable2:"+ofNullable2);

结果:
of1:Optional[Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)]
ofNullable1:Optional[Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)]
ofNullable2:Optional.empty

2、获取类方法

①.Optional.get()

/**
  如果存在值,则返回该值,否则抛出NoSuchElementException 。
  返回值:此Optional描述的非null值
  抛出:NoSuchElementException如果不存在任何值
  api注意:此方法的首选替代方法是orElseThrow() 
*/
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

②.Optional.orElse(T other)

/**
  如果存在值,则返回该值,否则返回other 。
  参数:其他–要返回的值(如果不存在任何值)。 可以为null 。
  返回值:值(如果存在),否则other
*/
public T orElse(T other) {
    return value != null ? value : other;
}

③.Optional.orElseGet(Supplier<? extends T> other)

/**
如果存在值,则返回该值,否则返回由供应函数supplier产生的结果。
返回值:如果不存在值,且supplier为null,则抛出NullPointerException
*/
public T orElseGet(Supplier<? extends T> supplier) {
    return value != null ? value : supplier.get();
}

④.Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)

/**
 与get 方法相同
*/
public T orElseThrow() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}
/**
如果存在值,则返回该值,否则抛出由异常提供函数产生的异常。
	类型参数:<X> –引发的异常类型
    返回值:值(如果存在)
    抛出:X –如果不存在任何值NullPointerException如果不存在任何值并且异常提供函数为null
    api注意:带有空参数列表的对异常构造函数的方法引用可用作提供者
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

⑤.or(Supplier<? extends Optional<? extends T>> supplier)

/**如果值存在时,返回一个Optional描述的值,否则将返回一个Optional产生通过供给功能。   
	参数:供应商–产生要返回的Optional的供应功能
    返回值:返回一个Optional描述此的值Optional ,如果一个值存在,否则Optional所生产的供应功能。
    抛出:NullPointerException如果提供的函数为null或产生null结果
 * @since 9
 */
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
        return this;
    } else {
        @SuppressWarnings("unchecked")
        Optional<T> r = (Optional<T>) supplier.get();
        return Objects.requireNonNull(r);
    }
}

⑥.示例代码

@Test
public void getTest02(){
    Trade trade = Trade.builder ()
        .iCode ("qyz0711")
        .iName ("青衣醉0711")
        .iType ("0711")
        .build ();
    Trade defaultTrade = Trade.builder ()
        .iCode ("MR1016")
        .iName ("默认值")
        .iType ("1016")
        .build ();
    Optional<Trade> tradeInfoEmptyOpt = Optional.empty();
    Optional<Trade> tradeInfoOpt = Optional.of(trade);
    // 1、get()-> 直接获取,注意如果value==null,会报NoSuchElementException异常
    Trade trade1 = tradeInfoOpt.get();
    System.out.println ("1、get():"+trade1);

    //2、orElse(T other)-> orElse可以传入一个Trade类型的参数当默认值,若value==null,则返回会默认值,否则返回value
    Trade trade2 = tradeInfoEmptyOpt.orElse (defaultTrade);
    Trade trade3 = tradeInfoOpt.orElse (defaultTrade);
    System.out.println ("2、orElse(),value == null:"+trade2);
    System.out.println ("2、orElse(),value != null:"+trade3);

    //3、orElseGet(Supplier<? extends T> other)-> 如果存在值,则返回该值,否则返回由供应函数supplier产生的结果。
    // 如果不存在值,且supplier为null,则抛出NullPointerException,和orElse不同的是orElseGet可以传入一段lambda表达式;
    Trade trade4 = tradeInfoEmptyOpt.orElseGet (() -> defaultTrade);
    Trade trade5 = tradeInfoOpt.orElseGet (() -> defaultTrade);
    //Trade trade6 = tradeInfoEmptyOpt.orElseGet (null);不存在值,且supplier为null,则抛出NullPointerException
    System.out.println ("3、orElseGet(other),value == null:"+trade4);
    System.out.println ("3、orElseGet(other),value != null:"+trade5);

    //4、orElseThrow(Supplier<? extends X> exceptionSupplier)-> 可以传入一段lambda表达式,
    // lambda返回一个Exception;当value!=null时,返回value值;当value==null时,抛出该异常;。
    Trade trade7 = tradeInfoOpt.orElseThrow (NullPointerException::new);
    System.out.println ("4、orElseThrow(exceptionSupplier),value == null:"+trade7);

    //5、or(Supplier<? extends Optional<? extends T>> supplier)->如果值存在时,返回一个Optional描述的值,否则将返回一个Optional产生通过供给功能。
    Optional<Trade> optTrade = tradeInfoEmptyOpt.or(()->{
        return Optional.of (Trade.builder ()
                            .iCode ("OR0714")
                            .iName ("OR0714")
                            .iType ("0714")
                            .build ());
    });
    if (optTrade.isPresent ()) {
        System.out.println ("5.or(supplier)"+optTrade.get ());
    }

}


结果:
1、get():Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)
2、orElse(),value == null:Trade(iName=默认值, iCode=MR1016, iType=1016)
2、orElse(),value != null:Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)
3、orElseGet(other),value == null:Trade(iName=默认值, iCode=MR1016, iType=1016)
3、orElseGet(other),value != null:Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)
4、orElseThrow(exceptionSupplier),value == null:Trade(iName=青衣醉0711, iCode=qyz0711, iType=0711)
5、or(supplier)Trade(iName=OR0714, iCode=OR0714, iType=0714)  

3、判断类方法

①.Optional.isPresent()

/**
	判断是否存在值
*/
public boolean isPresent() {
    return value != null;
}

②.Optional.ifPresent(Consumer<? super T> consumer)

/**
	如果存在值,则使用该值执行给定的操作,否则不执行任何操作。
    参数:Consumer要执行的动作
*/
public void ifPresent(Consumer<? super T> action) {
    if (value != null) {
        action.accept(value);
    }
}

3.Optional.ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

/**
    如果存在值,则使用该值执行给定的操作,否则执行给定的基于空的操作。
    参数:动作–要执行的动作(如果存在值)emptyAction –要执行的基于空的操作(如果不存在任何值)
    抛出:如果存在一个值并且给定的操作为null ,或者不存在任何值并且给定的基于空的操作为null ,
    则抛出NullPointerException;
*/
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}

④.示例代码

 public void ifTest03(){
     Trade trade = Trade.builder ()
         .iCode ("qyz0711")
         .iName ("青衣醉0711")
         .iType ("0711")
         .build ();
     Trade defaultTrade = Trade.builder ()
         .iCode ("MR1016")
         .iName ("默认值")
         .iType ("1016")
         .build ();
     Optional<Trade> tradeInfoEmptyOpt = Optional.empty();
     Optional<Trade> tradeInfoOpt = Optional.of(trade);
     // 1、isPresent()-> 判断是否存在对象,我们在调用get之前,一定要先调用isPresent,因为直接如果value是null,直接调用get会报异常;
     if (tradeInfoEmptyOpt.isPresent ()) {
         Trade trade1 = tradeInfoOpt.get ();
         System.out.println ("isPresent():"+trade1);
     }else {
         System.out.println ("isPresent(): null");
     }
     //2、ifPresent(Consumer<? super T> consumer)->如果存在值,则使用该值执行给定的操作,否则不执行任何操作。
     tradeInfoOpt.ifPresent (trade2 -> {
         System.out.println("ifPresent(Consumer<? super T> consumer):"+trade2);
     });

     //3、ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) -> 如果存在值,则使用该值执行给定的操作,否则就执行为空是的操作。
     tradeInfoOpt.ifPresentOrElse (trade3 -> {
         System.out.println("ifPresentOrElse():"+"trade3 is not null,"+trade3);
     },()->{
         System.out.println("ifPresentOrElse():"+"value is null");
     });
 }

4、过滤类方法

①.Optional filter(Predicate<? super predicate)

/**
  如果存在一个值,并且该值与给定的(predicate方法)匹配,则返回描述该值的Optional ,否则返回一个空的Optional 。
*/
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }
}

②.示例代码

@Test
public void filterTests(){
    //Optional<T> filter(Predicate<? super T> predicate)->如果存在一个值,并且该值与给定的(predicate方法)匹配,
    // 则返回描述该值的Optional ,否则返回一个空的Optional 。
    Optional<Object> o = Optional.ofNullable ("3333");
    System.out.println ("filter(predicate):"+o);
    Optional<Object> o2 = o.filter (o1 -> false);
    Optional<Object> o3 = o.filter (o1 -> true);
    System.out.println ("filter(predicate),predicate=false:"+o2);
    System.out.println ("fiilter(predicate),predicate=true:"+o3);
}

5.映射类方法

①.Optional map(Function<? super T,? extends U> mapper)

/**
  map方法接受一个映射函数参数,返回一个被Optional包装的结果。若结果为空,则返回 空Optional 。
  如果映射函数返回null结果,则此方法返回空的Optional 
  返回值类型:Optional对象
 */
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));
    }
}

②. Optional flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

/**
  flatMap 方法则要求入参中的函数式接口返回值本身就是 Optional 对象。
  返回值:施加的结果Optional可以映射函数此的值Optional ,如果一个值存在,否则一个空Optional
  抛出:NullPointerException如果映射函数为null或返回null结果
 */
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        @SuppressWarnings("unchecked")
        Optional<U> r = (Optional<U>) mapper.apply(value);
        return Objects.requireNonNull(r);
    }
}

③.map 与 flatmap 的区别及示例代码

相同点:两个方法的返回值都是 Optional 对象!

不同点:区别在于调用两个方法需要的传入参数(函数式接口)的返回值不同,map 方法会自动将入参中的函数式接口返回值包装成 Optional 对象,而 flatMap 方法则要求入参中的函数式接口返回值本身就是 Optional 对象。

入参的不同也就导致了使用场景的不同,如果函数式接口返回值本身就是 Optional 对象,那么就可以直接使用 flatMap 方法,否则就应该使用 map 方法,省去了使用 Optional 包装的麻烦。

比如示例中 toString 返回值是 String 类型,那么此时使用 map 方法更合理。

 @Test
public void mapTest(){
    Trade trade = new Trade ("qyz0711", "青衣醉0711", "0711");
    //1、map(Function<? super T,? extends U> mapper)->如果存在value,则通过mapper方法处理,
    // 映射称该值结果并返回,结果类型还是为Optional包装的对象;若不存在这返回空的Optional对象
    Optional<Trade> optionalTrade = Optional.of (trade);
    Optional<String> s = optionalTrade.map (Trade::toString);
    System.out.println (s);
    System.out.println (s.get ());

    //2、flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
    Optional<Trade> optionalTrade2 = Optional.of (trade);
    Optional<String> s1 = optionalTrade2.flatMap (trade1 -> {
        return Optional.ofNullable (trade1.getICode ());
    });

}

四、java9新特性

Java 9 增强

我们介绍了 Java 8 的特性,Java 9 为 Optional 类添加了三个方法:*or()*、*ifPresentOrElse()* 和 *stream()*。

or() 方法与 orElse()orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。

如果对象包含值,则 Lambda 表达式不会执行:

@Test
public void whenEmptyOptional_thenGetValueFromOr() {
    User result = Optional.ofNullable(user)
      .or( () -> Optional.of(new User("default","1234"))).get();
}

上面的示例中,如果 user 变量是 null,它会返回一个 Optional,它所包含的 User 对象,其电子邮件为 “default”。

ifPresentOrElse() 方法需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable

如果你想在有值的时候执行某个动作,或者只是跟踪是否定义了某个值,那么这个方法非常有用:

Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),
  () -> logger.info("User not found"));

最后介绍的是新的 stream() 方法,它通过把实例转换为 *Stream* 对象,让你从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。

我们来看一个把 Optional 处理成 Stream 的例子:

@Test
public void whenGetStream_thenOk() {
    User user = new User("john@gmail.com", "1234");
    List<String> emails = Optional.ofNullable(user)
      .stream()
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
      .map( u -> u.getEmail())
      .collect(Collectors.toList());

    assertTrue(emails.size() == 1);
    assertEquals(emails.get(0), user.getEmail());
}

这里对 Stream 的使用带来了其 filter()、map()collect() 接口,以获取 List

五、什么场景用Optional?

以前一直不懂Optional有啥用,感觉太无语了,Java8还把它当做一个噱头来宣传,最近终于发现它的用处了,当然不用函数式编程的话,是没感觉的;

如下提供了几个应用场景,基本上都是开发当中经常遇到的。

1.场景一

PatientInfo patientInfo = patientInfoDao.getPatientInfoById(consultOrder.getPatientId());
if (patientInfo != null) {
    consultInfoResp.setPatientHead(patientInfo.getHead());
}

// 使用Optional 和函数式编程,一行搞定,而且像说话一样
Optional.ofNullable(patientInfo).ifPresent(p -> consultInfoResp.setPatientHead(p.getHead()));

2.场景二

public void test1() throws Exception {
    Student student = new Student(null, 3);
    if (student == null || isEmpty(student.getName())) {
        throw new Exception();
    }
    String name = student.getName();
    // 业务省略...

    // 使用Optional改造
    Optional.ofNullable(student).filter(s -> !isEmpty(s.getName())).orElseThrow(() -> new Exception());
}

public static boolean isEmpty(CharSequence str) {
    return str == null || str.length() == 0;
}

3.场景三

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    if (comp != null) {
        CompResult result = comp.getResult();
        if (result != null) {
            User champion = result.getChampion();
            if (champion != null) {
                return champion.getName();
            }
        }
    }
    throw new IllegalArgumentException("The value of param comp isn't available.");
}

这个在开发中是很常见的一种逻辑。去判读传进来的参数时候为空,或者是从数据库中获取的对象。由于某些原因,我们不能很流程的直接这样写。

comp.getResult().getChampion().getName()

上面的写法用Optional改写:

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    return Optional.ofNullable(comp)
            .map(Competition::getResult)  // 相当于c -> c.getResult(),下同
            .map(CompResult::getChampion)
            .map(User::getName)
            .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}

4.场景四

类型之间的转换,并且当没有值的时候返回一个默认值

int timeout = Optional.ofNullable(redisProperties.getTimeout())
					  .map(x -> Long.valueOf(x.toMillis()).intValue())
					  .orElse(10000);

站在巨人肩膀上摘苹果

https://xie.infoq.cn/article/99383fd2bff1f7daa56b585f2

https://www.cnblogs.com/zhangboyu/p/7580262.html

https://blog.csdn.net/weixin_43888891/article/details/124788806

posted @ 2023-01-10 13:40  未月廿三  阅读(60)  评论(0编辑  收藏  举报