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";
    /**
     * 传统方法
     * @param insurance
     * @return
     */
    public static String getInsuranceName(Insurance insurance){
        if (Objects.isNull(insurance)){
            return UNKNOWN;
        }
        return insurance.getName();
    }
    /**
     * map的方式提取
     * @param insurance
     * @return
     */
    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;
    }

    /**
     * 模拟得到最便宜的保险
     *
     * @param person 人
     * @param car    车
     * @return 最便宜的车险名称
     */
    private static String getCheapest(Person person,Car car) {
        return "pinan";
    }

filter方法 (过滤)

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

对比代码:

    public static Insurance getPinanInsurance(Person person){
        Optional<Insurance> 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<String,Object> 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<String,"ccc");

        Object value = map.get(key);

        return Optional.ofNullable(value).orElseThrow(()->new NoSuchElementException("不存在key"));
    }

发生异常的建模可以替换为Optional对象

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

 /**
     * 如果字符串不是数字,会抛出异常
     * @param a 字符串
     * @return 数字
     */
    public Integer string2Int(String a){
        return Integer.parseInt(a);
    }

    /**
     * Optional.empty对应异常的情况,后续比较好处理;
     * @param a 字符串
     * @return 可能转换失败的整数,延迟到使用方去处理
     */
    public Optional<Integer> 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对象实例。
/**
 * A container object which may or may not contain a non-{@code null} value.
 * If a value is present,{@code isPresent()} returns {@code true}. If no
 * value is present,the object is considered <i>empty</i> and
 * {@code isPresent()} returns {@code false}.
 *
 * <p>Additional methods that depend on the presence or absence of a contained
 * value are provided,such as {@link #orElse(Object) orElse()}
 * (returns a default value if no value is present) and
 * {@link #ifPresent(Consumer) ifPresent()} (performs an
 * action if a value is present).
 *
 * <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
 * class; use of identity-sensitive operations (including reference equality
 * ({@code ==}),identity hash code,or synchronization) on instances of
 * {@code Optional} may have unpredictable results and should be avoided.
 *
 * @apiNote
 * {@code Optional} is primarily intended for use as a method return type where
 * there is a clear need to represent "no result," and where using {@code null}
 * is likely to cause errors. A variable whose type is {@code Optional} should
 * never itself be {@code null}; it should always point to an {@code Optional}
 * instance.
 *
 * @param <T> the type of value
 * @since 1.8
 */

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

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null,the value; if null,indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance,{@link Optional#EMPTY},* should exist per VM.
     */
    private Optional() {
        this.value = null;
    }

小结

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

原创不易,转载请注明出处。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式。 相信很多开发者都看到如下异步编程实践原则: 遵守以上冷冰冰的②③条的原则,可保证异步程序按照预期状态正常运作;我们在各大编程论坛常看到违背
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件。 每个组件是pipeline 中的一环。 自行决定是否将请求传递给下一个组件 在处理管道的下个组件执行之前和之后执行业务逻辑 二. 特性和行为 ASP.NET Core处
背景 在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。 Task&#160;表示无返回值的异步操作, 泛型版本Task&lt;TResult&gt;表示有返
HTTP基本认证 在HTTP中,HTTP基本认证(Basic Authentication)是一种允许网页浏览器或其他客户端程序以(用户名:口令) 请求资源的身份验证方式,不要求cookie,session identifier、login page等标记或载体。 - 所有浏览器据支持HTTP基本认
1.Linq 执行多列排序 OrderBy的意义是按照指定顺序排序,连续两次OrderBy,后面一个有可能会打乱前面一个的排序顺序,可能与预期不符。 要实现sql中的order by word,name类似效果; LINQ 有ThenBy可以紧接使用, ThenBy记住原本排序的值,然后再排其他值,
ASP.NET Core 核心特性:开源、跨平台、高性能是其决战JAVA的必胜法宝,最引人关注的跨平台特性 到底是怎么实现? &#xA; 本文分Unix、Windows剖析跨平台内幕,读完让你大呼过瘾。
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现。 IAsyncResult BeginGetResponse(AsyncCallback callback, object state
引言 最近在公司开发了一个项目,项目部署架构图如下: 思路 如图中文本所述,公司大数据集群不允许直接访问外网,需要一个网关服务器代理请求,本处服务器A就是边缘代理服务器的作用。 通常技术人员最快捷的思路是在服务器A上部署IISʺpplication Request Routing Module组件
作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。 但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序
引言 熟悉TPL Dataflow博文的朋友可能记得这是个单体程序,使用TPL Dataflow 处理工作流任务, 在使用Docker部署的过程中, 有一个问题一直无法回避: 在单体程序部署的瞬间(服务不可用)会有少量流量无法处理;更糟糕的情况下,迭代部署的这个版本有问题,上线后无法运作, 更多的流
合格的web后端程序员,除搬砖技能,还必须会给各种web服务器配置Https,本文结合ASP.NET Core部署模型聊一聊启用Https的方式。 温故知新 目前常见的Http请求明文传输,请求可能被篡改,访问的站点可能被伪造。 HTTPS是HTTP加上TLS/SSL协议构建的可进行加密传输、身份认
长话短说 前文《解剖HttpClientFactory,自由扩展HttpMessageHandler》主要讲如何为HttpClientFactory自定义HttpMessageHandler组件, 现在来完成课后的小作业: 将重点日志字段显示到Nlog的LayoutRenderer上。 本文实现一个
引言问题 作为资深老鸟,有事没事,出去面试;找准差距、定位价值。 面试必谈哈希, Q1:什么是哈希? Q2:哈希为什么快? Q3:你是怎么理解哈希算法利用空间换取时间的? Q4:你是怎么解决哈希冲突的? Q5:你有实际用写过哈希算法吗? 知识储备 哈希(也叫散列)是一种查找算法(可用于插入),哈希算
前言 如题,有感于博客园最近多次翻车,感觉像胡子眉毛一把抓, 定位不了生产环境的问题。 抛开流程问题,思考在生产环境中如何做故障排除,&#160;发现博客园里面这方面的文章比较少。 .Net 本身是提供了sos.dll工具帮助我们在生产中故障排除,通过提供有关内部公共语言运行时(CLR)环境的信息,
.NET程序是基于.NET Framework、.NET Core、Mono、【.NET实现】开发和运行的 ,定义以上【.NET实现】的标准规范称为.NET Standard .NET Standard .NET标准是一组API集合,由上层三种【.NET实现】的Basic Class Library
长话短说 上个月公司上线了一个物联网数据科学项目,我主要负责前端接受物联网事件,并提供 参数下载。 webapp 部署在Azure云上,参数使用Azure SQL Server存储。 最近从灰度测试转向全量部署之后,日志时常收到: SQL Session超限报错。 排查 我在Azure上使用的是 S
临近年关,搜狗,360浏览器出现页面无法成功跳转,同域Cookie丢失? 也许是服务端 SameSite惹的祸。&#xA;本文揭示由于Chrome低版本内核不识别 SameSite= None, 引发的单点登录故障。
本文聊一聊TraceID的作用和一般组成,衍生出ASP. NETCore 单体和分布式程序中 TraceId 的使用方式
通过给 HttpClint请求的日志增加 TraceId,解锁自定义扩展 HttpClientFacroty 的姿势