优雅的剥洋葱:浅析 Java Optional 类

工具执行引擎核心设计:DAG并行、中间件与HITL重运行 在软件工程中,任务编排与自动化执行是构建复杂系统的关键技术。其核心原理在于将独立的功能单元抽象为节点,通过声明式依赖构建有向无环图,由执行引擎智能调度,实现基于数据依赖的并行执行,从而大幅提升处理效率。这一模式的技术价值在于解耦业务逻辑与执行控制,提供统一的错误处理、状态管理与可观测性。其典型应用场景包括智能对话系统、数据处理流水线、自动化运维等需要串联多个工具或服务的领域。本文聚焦的工具执行引擎,正是这一思想的实践,它通过DAG并行调度解决效率瓶颈,借助中间件洋葱模型实现日志、缓存等横切面管控,并引入HI 阅读详情

前言

杨宗纬唱过一首歌,叫做《洋葱》,里面有一句是:“如果你愿意一层一层一层的剥开我的心。。。”,歌曲是非常的感人。

其实在咱们程序员日常开发中,也经常会遇见需要一层一层剥洋葱的情况,如下图:

比方说,现在咱们在后端需要请求某个接口,该接口返回格式如图,咱们需要得到items数组里第一项的value标签的值,可以发现,这就是个复杂对象一层一层 get 也就是一层一层剥洋葱的情况,每一次 get 都有可能为空,为了防止发生 NPE(空指针异常),咱们可能每一次 get 都得用 if 进行一个判空,像上图这种情况,可能得套四五个 if,代码非常臃肿。

这也引出了本文的主题,Optional 类,此类可以用来解决上述问题。

Optional 类简析

类的结构和构造方法

Optional 类的结构非常简单,如下:

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

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

此类有一个私有属性:value,这是用来表示实际对象的,Optional 类其实算是个包装类,将要处理的对象置于value,后面会说到;一个静态变量EMPTY,其实是个饿汉模式的单例,表示空对象对应的 Optional 对象。

看完了它的属性,再看一下类的构造函数:

private Optional(T value) {
    this.value = value;
}
复制代码

类的构造方法非常简单,接收一个value 参数赋给自己的私有属性。注意到构造函数是 private 的,因此后面肯定有方法调用它来实例化对象的。

常用方法

of 方法

public static <T> Optional<T> of(T value) {
    return new Optional<>(Objects.requireNonNull(value));
}
复制代码

说曹操,曹操到。果然出现了调用构造方法的方法,还是赋值value属性,不过需要注意这里会调用Objects.requireNonNull(value),此方法还是会抛异常的,如下:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
复制代码

ofNullable 方法

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? (Optional<T>) EMPTY
                         : new Optional<>(value);
}
复制代码

和 of 方法不同的是,此方法会先判断要转换的对象是否为 null,若是则返回(Optional<T>) EMPTY,否则才会调用构造方法。可见,这个方法还是比较省心的,不用考虑太多 null 的问题,也不用担心抛 NPE。

判空方法

public boolean isPresent() {
    return value != null;
}

public void ifPresent(Consumer<? super T> action) {
    if (value != null) {
        action.accept(value);
    }
}
复制代码

两个方法比较类似,就放在一块了,区别就是一个只是判断value是否为 null,另一个在判为非 null 之后会调用传入的 Consumer 接口对象对value进行后续处理。

empty 方法

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}
复制代码

返回一个value为空的Optional对象。

map 方法

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, ? 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 方法有两个,都是先判断 value 是否为 null,若是,直接返回空的 Optional 类对象,否则,调用函数接口对象 mapper 的 apply 方法。

map 和 flatMap 方法的主要不同在于调用的函数接口对应的 apply 方法的返回值不同,map 方法返回的就是一个普通的类型,确定value非空后,会调用Optional.ofNullable()自动将mapper.apply(value)的返回值包成一个 Optional 类的对象,比较方便省心;而 flatMap 方法需要你提供的函数接口的 apply 方法本身就返回 Optional 类的对象,而且也有抛出 NPE 的风险,比较费心。

当然,也不能通过所谓的省心费心来判断哪个方法更好,有些情况下,选择 flatMap 是更合适的。

filter 方法

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    // 检查 value 是否为 null
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }
}
复制代码

代码也比较简单,先看看value是否为 null,若是,直接返回this,不做处理;否则,调用predicate.test(value)看看value是否满足我们定义的某种规则,若不满足,返回empty();,方法如其名,是一个用来过滤的方法。

orElse 方法

// value 为 null,返回默认值
public T orElse(T other) {
    return value != null ? value : other;
}
// value 为 null,返回函数接口计算的值
public T orElseGet(Supplier<? extends T> supplier) {
    return value != null ? value : supplier.get();
}
// value 为 null,抛出异常
public T orElseThrow() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}
复制代码

三个方法都是拿到value值的,拿之前会做是否为 null 的判断,为 null 时,三个方法各自选择不同的处理办法,可以根据情景选择合适的方法。

小结

看完了 Optional 类的结构和主要方法,可以先做个小结了:Optional 类其实算是个包装类,我们可以把想处理的对象包在 Optional 类里面,Optional 类将该对象置于value属性中,类本身提供了一些实用方法用来帮助对value进行判空,过滤等处理。简化了我们的开发。

优雅的剥洋葱

说了这么多,让我们回到前言里那个问题吧,看看怎么用 Optional 类来优雅的剥洋葱。

我们先构造一个符合这种格式的 Json 对象:

public static void main(String[] args) {
    JSONObject jsonData = JSON.parseObject(
        "{\"code\":200,\"msg\":\"success\",\"data\":{\"items\":[{\"value\":8},{\"value\":3}]}}");
    System.out.println(jsonData);
}
复制代码

假设现在我们请求某接口,得到的响应是上面的jsonData这个对象(这里我就不写接口了,简单模拟一下)。我们想得到 items 数组第一项的 value 标签值,可以用下面的写法来 get 想要的值。

Integer result = Optional.ofNullable(jsonData.getJSONObject("data"))
    .map(data -> data.getJSONArray("items"))
    .map(items -> items.getJSONObject(0))
    .map(item -> item.get("value"))
    .map(value -> value.toString())
    .map(Integer::valueOf)
    .orElse(0);
System.out.println(result);
复制代码

结果如下,符合预期

可以看出,这一连串的 get get get,如果不用 Optional ,那代码简直没法看了,这里代码里并没有判空操作,大家感兴趣可以对这个初始化的字符串进行修改,让某一次 get 为 null,照样不会抛出 NPE,这里我就不演示了。当然,这里我 get 不到的处理是返回一个默认值 0,具体使用哪个方法处理还得看你自己的实际情况。

我们来分析一下,为啥不用写判空代码了呢?

首先是调用 ofNullable 方法,得到一个 Optional 对象,接着调用一连串的 map 方法,如果任何一次 get 的结果为 null,通过上一节对 map 方法源码的解读,方法一进来会调用if (!isPresent())来判断value是否为 null,如果是的话,直接return empty();,那么下一次再调用 map 时,检查if (!isPresent())仍然通过不了,还是return empty();。所以只要某次 get 不到值,整个 map,filter,flatMap这一系列调用链都是返回一个value为空的 Optional 对象,直到最后的 orElse 或者 orElseThrow 这种方法来处理。

Optional 的优缺点之我见

Optional的优点不用说了,优雅,代码写出来链式调用,结构好看,不用大量的判空代码。不过个人感觉还是有一些缺点的:

  • 比较占空间,毕竟把原来的对象又包了一层,所以如果只是简单判断一个对象是否为null,个人感觉直接 if 判断可能更好一点,而且语义还更加清晰
  • 链式 map 的时候,无法得知具体哪一次 get 失败了
数据工程中FP与OOP的实战选型:何时用函数式,何时用面向对象 函数式编程(FP)和面向对象编程(OOP)是数据工程中两大核心范式,本质区别在于状态管理与计算可重现性。FP以纯函数、不可变数据和函数组合为特征,天然适配无状态的数据清洗、实时流处理与分布式计算场景,保障高并发下的确定性与可测试性;OOP则通过封装、继承与多态,有效管理数据库连接池、模型服务生命周期、外部API集成等有状态系统,提升可维护性与扩展性。在ETL流水线、机器学习平台、特征工程框架等典型数据系统中,二者并非对立,而是需依据数据规模、状态复杂度、变更频率等维度协同设计。本文基于金融风控、电商推荐等真 阅读详情

相关推荐

银行私有化部署Dify:四大安全加固与国密SM4集成实战

在数字化转型浪潮中,企业级AI应用开发平台成为提升业务智能化水平的关键工具。其核心原理在于通过大语言模型与低代码框架,降低智能应用构建门槛,实现智能客服、文档分析等场景的快速落地。对于数据安全与合规性要求极高的金融行业,私有化部署是保障核心数据资产可控的必然选择。在此基础上,构建纵深防御体系至关重要,涉及基础设施隔离、统一身份认证、数据全生命周期加密及运维审计等多个层面。其中,采用国密算法进行数据加密是满足国内监管要求的重要环节。本文聚焦于金融级高安全场景,详细解析如何将国密SM4算法集成至私有化部署的Di

congdi7904的博客 313

java8新特性二-stream流

链式编程 链式编程简单来说就是一个方法返回引用本身: public class demo { public static void main(String[] args) { Person person = new Person().setName("李四").setAge(18); System.out.println(person); } } class Person{ private String name; private int ag

qq_45891099的博客 862

ONNX模型生产部署:从封装、服务到监控的MLOps实战

机器学习模型上线并非终点,而是MLOps工程挑战的起点。理解模型服务化本质,需把握封装(隔离环境与定义输入输出契约)、服务(高并发、强校验、混沌韧性)和监控(基础设施、逻辑、模型三层立体观测)三大核心原理。其技术价值在于保障模型在真实流量、脏数据、依赖故障等复杂场景下的稳定性、可观测性与可维护性,广泛应用于金融风控、推荐系统、实时预测等关键业务。本文聚焦ONNX格式模型的标准化导出、FastAPI+Docker+Kubernetes生产级封装与部署,并深度融合Prometheus指标体系,覆盖从Notebo

weixin_34406086的博客 408

Java8————Lambda表达式(二)

译者注:文中内容均来自于官方教程《Lambda Expressions》,但是由于英汉语言的差异,部分语句官方描述过于冗余,因此译者根据通常状况的理解做了微调,但不会影响表达的含义。比如: 原文:You want to create a feature that enables an administrator to perform any kind of action, such as se...

Morty的技术乐园 769

Java Optional 实用判空实用实战,优雅永不过时

Optional 实用判空实用实战,优雅永不过时。平时写if null写到麻木不仁?

默默不代表沉默 2174

Qt + Robot Interface实现上位机和发那科机器人通信

摘要: 本文详细介绍了FANUC机器人的RobotInterface模块开发流程,包括环境配置、通信连接和上位机开发等内容,并提供了数值寄存器、位置寄存器和当前位置的读写代码等示例。适用于自动化行业有通讯需求的上位机开发人员,保姆教程,超详细版,新手也可以根据示例开发自己的通信程序。

xyhkma的博客 1750

Mybatis-Plus采坑记录

Mybatis-Plus自带 Page分页插件,而且和PageHelper有很多冲突的地方。 在遇到一些Mybatis-plus无法处理的复杂sql时可以用 apply 这个条件构造器比如QueryWrapper<UserDO> queryMapper = new QueryWrapper<>(); queryMapper.apply(!StringUtils.isEmpty(request.getCreateDate()),"DATE_FORMAT(CREATE_DATE,'%Y.

weixin_43522534的博客 615

用Matlab机器人工具箱搞定六轴机械臂建模:从DH参数到3D可视化(附完整代码)

本文详细介绍了如何使用Matlab机器人工具箱进行六轴机械臂建模,涵盖从DH参数转换到3D可视化的完整流程。通过正运动学验证、逆解计算和工作空间分析等实战案例,帮助开发者快速掌握机械臂建模技巧,并提供性能优化建议和完整代码示例。

weixin_28733637的博客 360

JAVA8如何妙用Optional解决NPE问题详解

JAVA8如何妙用Optional解决NPE问题详解 在Java中,null代表一个不存在的对象,如果对它进行操作就会抛出java.lang.NullPointerException异常,下面这篇文章主要给大家介绍了关于JAVA8如何妙用Optional解决NPE问题的相关资料,需要的朋友可以参考下 引言 NPE(NullPointerException)是调试程序最常见的异常。go...

songxiugongwang的博客 562

LIGGGHTS开源离散元仿真:从基础安装到高级应用实战

本文详细介绍了LIGGGHTS开源离散元仿真软件的基础安装、脚本编写及高级应用实战。从颗粒自由落体模拟到工业级复杂系统建模,涵盖CFD-DEM耦合、高性能计算优化等关键技术,帮助用户快速掌握这一强大的颗粒仿真工具。特别适合需要处理颗粒系统仿真的科研人员和工程师。

l1k9j8h7g6的博客 619

Cadence IC5141保姆级教程:Bandgap电路四大仿真(稳定性/噪声/启动/PSRR)完整流程与调参心得

本文提供Cadence IC5141中Bandgap电路的四大仿真(稳定性仿真、噪声仿真、启动仿真、PSRR仿真)的完整流程与调参心得。从原理图检查到ADE L配置,详细解析各仿真步骤的关键参数设置与优化策略,帮助工程师高效完成电路设计与性能调优。

weixin_30399821的博客 1651

Prompt装饰器模式:优雅解决大模型上下文管理与工程化难题

在大型语言模型(LLM)应用开发中,Prompt工程是连接用户意图与模型能力的关键桥梁。其核心原理在于通过精心设计的文本指令,引导模型生成符合预期的输出。然而,随着应用复杂度提升,硬编码的Prompt模板面临维护困难、复用性差等挑战,尤其是当遇到上下文窗口限制导致的'prompt too large for the model'错误时,传统方法往往显得力不从心。Prompt装饰器模式应运而生,它借鉴软件工程中的装饰器设计思想,将Prompt的构建过程解耦为可插拔、可组合的处理器链。每个装饰器负责一项特定任务

weixin_30266885的博客 310

(6)NUC980 SPI Nand 启动

方式一:下载到SPI-NAND-Flash中,这种方式可以将程序固化到SPIflash中,可以掉电保存。第二种方式下载到DDR中程序掉电丢失,但是DDR调试方便,时候调试的时候使用。之前的设置都是基于SD卡启动,今天测试一下是用SPI flash启动。方式二: 下载到DDR中,①设置为USB启动②下载到DDR中③运行。1:烧写BootLoader。

卤煮小鱼的博客 1754

可蒸发层架构:大模型如何让Prompt工程与RAG自动退场

在AI工程实践中,‘中间抽象层’(如Prompt工程、RAG预处理、LLM编排器)长期用于弥合模型能力与业务需求之间的语义鸿沟。其本质是因模型缺乏原生业务理解、约束感知与结构化决策能力而被迫引入的胶水逻辑。随着大模型向‘语义原生解释器’演进,具备语义锚定、约束感知推理和零延迟反馈闭环能力的新一代架构,正使这些中间层失去存在必要——不是被替代,而是被内化。这种‘可蒸发层’范式显著降低Token开销、端到端延迟与错误传播链,已在金融合规、医疗知识图谱、工业诊断等强约束场景验证落地。本文聚焦Anthropic提出

GOBY 栏目 507

王道考研计算机网络课件全解析:从零搭建你的知识框架(附B站视频+PPT下载)

本文全面解析王道考研计算机网络课程的核心知识体系,从OSI七层模型到TCP/IP四层架构,详细讲解物理层、数据链路层、网络层、传输层和应用层的关键技术。提供高效学习路径、实战案例和常见误区警示,帮助考生系统构建计算机网络知识框架,适合计算机考研备考者参考学习。

leaf8的博客 564

基于分位数回归的静态CoVaR计算 案例与代码

原文链接:https://bbs.pinggu.org/thread-7925441-1-1.html 一、静态CoVaR模型介绍 二、资料简介 实现对相关期刊论文进行论文重现,解决实证分析中的技术操作问题。里面包含了每一步详细的步骤,可以方便的利用这个手册解决大部分利用分位数回归计算静态CoVaR的论文的模型实现问题。即从数据下载到模型实现一整条操作步骤。 关键词:【分位数回归】【Co...

ChenQihome9的博客 7504

01:SpringAI

ai

weixin_43761434的博客 422

山羚羊优化算法(MGO)详解:从自然智慧到优化引擎

山羚羊优化算法(MGO)是一种新型元启发式算法,灵感来源于山羚羊的社会行为和等级制度。该算法模拟了四种核心行为:领地独居雄性(局部开发)、母系山瞪羚群(群体协作)、单身雄性山瞪羚(全局探索)和迁徙觅食(动态扰动)。MGO通过数学建模将这些生物行为转化为优化机制,在探索与开发间实现平衡。算法已成功应用于函数优化、无人机路径规划和支持向量机优化等领域,展现出优异的性能。未来研究将聚焦于参数自适应调整、混合算法设计等方向,以进一步提升算法性能。

qq_42212808的博客 203

Spring Boot 4 旅游主题实战教程 阶段四:缓存与底层进阶

阶段五章补齐高并发与横切能力:第12章用 StringRedisTemplate 操作五大类型,实现缓存/计数/排行榜;第13章以 @Cacheable/@CachePut/@CacheEvict 声明式缓存替代手写模板,并防御穿透/击穿/雪崩;第14章拆解 AOP(切面/切点/五种通知),揭开 @Cacheable 底层代理之谜;第15章用 @Async 线程池异步化慢任务、@Scheduled 定时调度,并开启虚拟线程;第16章以 Slf4j+Logback 实现分级日志、按天滚动与 ERROR 分家。

桃李不言下自成蹊 332

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

python3 核心笔记

本文主要是针对python3的一些核心要点做一些记录,感兴趣的可以关注和订阅。会持续更新,这个玩意学习一下,可以为数据分析和AI打下一定的基础。

yanghuashuiyue的专栏 618

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Java8新特性-Optional,在线面试指南

orElseThrow()方法其实与orElseGet()方法非常相似了,入参都是Supplier对象,只不过orElseThrow()的Supplier对象必须返回一个Throwable异常,并在orElseThrow()中将异常抛出,orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。orElseGet()方法与orElse()方法类似,区别在于orElseGet()方法的入参为一个Supplier对象,用Supplier对象的get()方法的返回值作为默认值。

2401_84419309的博客 889

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

干货 | DDD实战:基于洋葱模型的分层代码架构设计

点击上方“中兴开发者社区”,关注我们 每天读一篇一线开发者原创好文 ▎作者简介 作者冯丹是一名非常有激情的一线程序员,喜欢java强大的面向对象能力,scala简洁的函数式编程范式以及Akka这种优秀的响应式编程框架。今天的文章可以让读者了解DDD落地的一种具体的措施。 领域驱动设计DDD(Domain Driven Design)的主旨思想就是不再把需求分析和代码实现分解

中兴开发者社区 1万+

谈谈Java Optional的坑

不推荐使用,只能判断对象,而省略掉的if/else在某种程度上,虽然是优化了代码却是造成了更多的问题。同时更多的对象new出,造成内存的开销很大。并且在后来的资料查询中发现很长时间没有更新了(最新版为java11)。从22年java19出现,多个版本未发生变化,不知道是否已经放弃。而国内市面上流行的版本依旧是Java8,也就是说9-11的新特性是无法使用的。

zth_killer的博客 813

Optional 不建议的使用场景有哪些?

Optional是一个有用的工具,可以帮助开发者处理可能为空的返回值,但它并不适用于所有场景。要确保在适当的场合使用Optional,并避免在不合适的地方滥用它,从而保持代码的简洁和可读性。

KimSoft's Blog 731

Java8新特性:Optional 类

设计初衷不同:Java 的Optional主要用于减少空指针异常,提高代码的安全性和可读性。相比之下,Scala 的Option和 Kotlin 的Nullable类型设计初衷更加广泛。API 风格:Java 的Optional提供了一系列 API 方法,方便进行链式调用和流处理。这些方法与 Java 8 引入的流操作紧密结合。性能考虑:在高并发环境下,Optional的设计考虑了性能优化,但需要开发者自己注意避免频繁创建对象。Optional。

qq_33326733的博客 1145
上一篇: 想到多线程并发就心虚?先来巩固下这些线程基础知识吧!
下一篇: 闭关28天,奉上[Java一线大厂高岗面试题解析合集],备战金九银十
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值