栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java8-Optional类

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java8-Optional类

背景

NPE问题,100%的Java程序员都碰到,并且曾经是心中的痛。
1965年英国TonyHoare引入了Null引用,后续的设计语言包括Java都保持了这种设计。

一个例子

业务模型

Person 有车一族, 有Car字段,

Car 车,每个车都有购买保险, 有Insurance字段;

Insurance 保险,每个保险都有名字 有name字段;

需求:获取某个Person对象的购买保险的名称;


常规编程

    public String getCarInsuranceName(Person person) {
 return person.getCar().getInsurance().getName();
    }

检查式编程

 public String getCarInsuranceName_check(Person person) {
 if (Objects.nonNull(person)) {
     final Car car = person.getCar();
     if (Objects.nonNull(car)) {
  final Insurance insurance = car.getInsurance();
  if (Objects.nonNull(insurance)) {
      return insurance.getName();
  }
     }
 }
 return "unkown";
    }

防御式编程

public String getCarInsuranceName_protect(Person person) {
 if (Objects.isNull(person)) {
     return "unkown";
 }
 final Car car = person.getCar();
 if (Objects.isNull(car)) {
     return "unkown";
 }
 final Insurance insurance = car.getInsurance();
 if (Objects.isNull(insurance)) {
     return "unkown";
 }
 return insurance.getName();
    }

对比一下缺点:

编程方法 缺点
常规编程 NPE问题
检查式编程 1.可读性不好,多层if嵌套; 2.扩展性不好,需要熟悉全流程,否则不知道应该在哪个if中扩展,极易出错;
防御式编程 1. 维护困难,4个不同的退出点,极易出错,容易遗漏检查项目

NPE的痛点

  1. java程序中出现最多的Exception;没有之一;
  2. 使得代码量膨胀混乱,对象的空判断充斥在代码中,但是却没有实际的业务意义;
  3. 类型系统的一个后门,实际上不属于任何类型,也可以说是任何类型;
  4. 本身无意义,标识对缺失值的建模,也破坏了java中弱化指针的理念。

java8中对缺失值的建模对象是Optional,可以基于它解决NPE的痛点,设计更好的API

Optional

领域模型的建模进化

  1. Person , 含有一个Optional car字段,一个人可能有车,也可能没有车;
  2. Car, 包含有一个Optional insurance字段, 一台车可能买了保险,也可能没有买保险;
  3. Insurance , 保险公司必定有名字所有,他有一个字段 name;
构造方法
构造方法 说明 备注
Optional.empty() 一定是空的对象 跟null有区别,是一个单例对象
Optional.of(T t) 一定是不空的对象 如果给了null值会立刻抛出NPE
Optioanl.ofNullable(T t) 允许为空的对象放在里面 使用值之前需要做检查
map方法-对象中提取和转换值

可以把Optional看成一种单元素的Stream, Map,即把其中的元素按照一定规则转换为其它类型或者进行其它运算后的值,如果没有元素,则啥也不做。

下面的代码是等同的。

public class Test {
    public static final String UNKNOWN = "unknown";
    
    public static String getInsuranceName(Insurance insurance){
 if (Objects.isNull(insurance)){
     return UNKNOWN;
 }
 return insurance.getName();
    }
    
    public static String getInsuranceNameOp(Insurance insurance){
return Optional.ofNullable(insurance).map(Insurance::getName).orElse(UNKNOWN);
    }
}
flatMap方法 - 转换为Optional对象输出;

类似于Stream的flatMap方法,把元素切割或者组合成另外一个流输出。

    public static String getInsuranceName(Person person) {
 return Optional.ofNullable(person)
  .flatMap(Person::getCar)
  .flatMap(Car::getInsurance)
  .map(Insurance::getName).orElse(UNKNOWN);
    }
默认值设置方法(5种适合不同的场景)
默认值方法 说明 场景
or(Supplier) 为空则延迟构造一个Optional对象 可以采用延迟的方式,对接某些代码来产生默认值
orElse(T t) 为空则采用默认值 直接,简单
orElseGet(Supplier sp) 为空则通过函数返回 延迟返回,可以对接某些代码逻辑
orElseThrow() 为空则跑出异常 默认是NoSuchElementException
orElseThrow(Supplier sp) 为空则跑出自定义异常 异常类型可以自定义
使用值(获取或者消费)

主要分两种场景,直接获取值, 采用get()方法;

里面有值,则消费, ifPresent(Consumer c)

消费或者获取方法 说明 场景
get() 获取Optional中的值,如果没有值,会抛出异常 确认里面有值才会调用该防范
ifPresent(Consumer c) 有值则执行自定义代码段,消费该值 流式编程,有值继续处理逻辑
ifPresentOrElse(Consumer c , Runnable r) 如果有值,则消费,没有值,进行另外的处理 有值或者没有值都进行处理java9才有
多个Optional进行运算

通过使用flatMap,map可以做到,方法里执行的已经做好了对empty的情况进行处理。

实例如下:


    public static String getCheapestPrizeIsuranceNameOp(Person person, Car car) {
 return Optional.ofNullable(person)
  .flatMap(p -> Optional.ofNullable(car).map(c -> getCheapest(p, c)))
  .orElse(UNKNOWN);
    }

    public static String getCheapestPrizeIsuranceName(Person person, Car car) {
 if (Objects.nonNull(person) && Objects.nonNull(car)) {
     return getCheapest(person, car);
 }
 return UNKNOWN;
    }

    
    private static String getCheapest(Person person, Car car) {
 return "pinan";
    }
filter方法 (过滤)

因为Optional中只有一个值,所以这里的filter实际上是判断单个值是不是。

对比代码:

    public static Insurance getPinanInsurance(Person person){
 Optional insuranceOptional = Optional.ofNullable(person).map(Person::getCar).map(Car::getInsurance);
 if (insuranceOptional.isPresent() &&  Objects.equals("pinan", insuranceOptional.get().getName())){
     return insuranceOptional.get();
 }
 return null;
    }

    public static Insurance getPinanInsurance_filter(Person person){
 return Optional.ofNullable(person)
  .map(Person::getCar)
  .map(Car::getInsurance)
  .filter(item->Objects.equals(item.getName(),"pinan" ))
  .orElse(null);
    }
empty方法 (构造一个空的Optional对象)
Optional改造历史代码 封装可能潜在为null的对象
    public Object getFromMap(String key){

 Map map = new HashMap<>(4);
 map.put("a", "aaa");
 map.put("b", "bbb");
 map.put("c", "ccc");

 Object value = map.get(key);
 if (Objects.isNull(value)){
     throw new NoSuchElementException("不存在key");
 }
 return value;

    }


    public Object getFromMapOp(String key){

 Map map = new HashMap<>(4);
 map.put("a", "aaa");
 map.put("b", "bbb");
 map.put("c", "ccc");

 Object value = map.get(key);

 return Optional.ofNullable(value).orElseThrow(()->new NoSuchElementException("不存在key"));
    }
发生异常的建模可以替换为Optional对象

这种是建模思想的转变,不一定适用每个人;

 
    public Integer string2Int(String a){
 return Integer.parseInt(a);
    }

    
    public Optional string2Int_op(String a){
 try{
     return Optional.of(Integer.parseInt(a));
 }catch (Exception ex){
     return Optional.empty();
 }
    }
尽量不使用封装的Optional

封装的OptionalInt, OptionalLong ,因为Optional里面只有一个元素,使用封装类没有性能优势,而且缺失了重要的flatMap, map,filter方法;

总的来说,Optional的使用,简化了代码,使得代码可读性和可维护性更好。

最后来个例子:

    public Integer getFromProperties(Properties properties, String key) {
 String value = properties.getProperty(key);
 if (Objects.nonNull(value)) {
     try {
  Integer integer = Integer.parseInt(value);
  if (integer > 0) {
      return integer;
  }
     } catch (Exception ex) {
  //无需处理异常
  return 0;
     }
 }
 return 0;
    }


    public Integer getFromProperties_op(Properties properties, String key) {
 return Optional.ofNullable(properties.getProperty(key))
  .map(item -> {
      try {
   return Integer.parseInt(item);
      } catch (Exception ex) {
   return 0;
      }
  })
  .orElse(0);
    }
Optional源码阅读
一个容器对象,可能有也可能没有非空值,如果值存在,isPresent()返回true,如果没有值,则对象被当成空,isPresent()返回false;
更多的方法依赖于容器中是否含有值,比如orElse(返回一个默认值当没有值)
ifPresent(Consumer c) 是当值存在的时候,执行一个动作;

这是一个基于值的类,使用标识敏感的操作,包含 比较引用的 == , hashcode , synchronization  针对一个Optional对象,可能有无法预料的结果,然后应该避免这类操作。

编写API的注意点:
Optional最初被用来设计为方法的返回值,当明确需要代表没有值的情况。
返回null,可能出错;而返回Optional对象不是一个null对象,它总是指向一个Optional对象实例。

其它的代码比较简单,模型就是里面含有一个T类型的值,empty()是一个特殊的Optional对象,里面的值是null;

public final class Optional {
    
    private static final Optional EMPTY = new Optional<>();

    
    private final T value;

    
    private Optional() {
 this.value = null;
    }
小结
  1. Optional表示一个可能缺失的对象,API可以依据这个进行建模,但是要注意序列化的问题;可以避免空指针的问题,并且提升代码的可读性和可维护性。
  2. Optional的构造方法有3个,of,ofNullable,empty;
  3. map,flatmap , filter 可以快速的转换和过滤值;
  4. 值缺失的处理方法有3个,orElse, orElseGet, orElseThrow;

本文由博客一文多发平台 OpenWrite 发布!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/239367.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号