JDK8:Optional详解与源码分析,如何优雅的处理空指针

news2025/1/18 5:56:18

文章目录

  • 一、Optional概述
    • 1、烦人的NullPointerException
    • 2、Optional简介
  • 二、Optional使用
    • 1、创建Optional对象
    • 2、isPresent()与ifPresent()应用&源码解析
    • 3、get()应用&源码解析
    • 4、orElseThrow()应用&源码解析
    • 5、map()应用&源码解析
    • 6、flatMap()应用&源码解析
    • 7、filter()应用&源码解析
    • 8、orElse()应用&源码解析
    • 9、orElseGet()应用&源码解析
    • 10、orElseThrow()应用&源码分析

一、Optional概述

1、烦人的NullPointerException

在日常开发中,NullPointerException相信所有人都见过,不管你是刚入行的萌新还是骨灰级玩家,对于它都是耳熟能详的。它的出现可以说无处不在,总是能在各种场景下出现。那么对于如何防止它的出现,我们平时都是被动的采用各种非空校验,但是它还是经常能出现在我们的视线中。

public String getCompanyName(Student student){
	
	if (student != null){
		Job job = student.getJob();
		
		if (job != null){
			Company company = job.getCompany();
			if (company != null){
				String name = company.getName();
				return name;
			}else {
				return "no company";
			}
		}else {
			return "no job";
		}
	}else {
		 return "no student";
	}
}

对于上述这段代码,相信大家平时工作类似的代码经常会有出现。每一次在获取到一个对象时都进行一个null的判断,然后才继续后续的实现。但是这种方式很不好,首先会存在大量的if-else判断嵌套,导致代码的可读性和扩展性极差。此时,有的同学可能就会使用卫语句这么改造,如下所示:

public String getCompanyName(Student student){
	if (student == null){
		return "no student";
	}
	Job job = student.getJob();
	if (job == null){
		return "no job";
	}
	Company company = job.getCompany();
	if (company == null){
		return "no company";
	}
	return company.getName();
}

这种判断已经有意识的避免了大量嵌套判断,但是同样存在多个不同的判断点,代码维护同样困难。

2、Optional简介

为了防止空指针异常的出现,Java8中引入了一个新类Optional,对于它之前我们已经进行了简单的实现。其本质就是通过Optional类对值进行封装, 当有值的时候,会把该值封装到Optional类中。如果没有值的话,则会在该类封装一个Empty。

二、Optional使用

在Optional类中基于函数式接口提供了一些用于操作值的方法。
在这里插入图片描述

1、创建Optional对象

创建Optional,该类提供了三种方法操作,分别为:empty()、of()、ofNullable()。使用方式如下所示:

Optional<Student> studentOptional = Optional.empty();
Optional<Student> studentOptional = Optional.of(student);
Optional<Student> studentOptional = Optional.ofNullable(student);

这三者有什么区别?根据源码分析如下:

// 源码分析
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY; // private static final Optional<?> EMPTY = new Optional<>();
    return t;
}
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

根据源码可知,empty()会直接返回一个空的Optional实例,内部不会存在任何值。

of()会返回一个存在值的Optional对象,并且该值不允许null的存在。如果调用该方法时传入参数是null,则立刻抛出NullPointerException,而不是等到你用这个对象时才抛出,相当于进行了立即检查。

ofNullable()同样也会返回一个存在值的Optional对象,但是它和of()最大的不同在于,它会对传入的值进行判断,如果传入的值为null,其会调用empty()返回一个不包含内容的Optional,如果不为null,则会调用of()返回一个包含内容的Optional。

2、isPresent()与ifPresent()应用&源码解析

Optional类中提供了两个方法用于判断Optional是否有值,分别是isPresent()和ifPresent(Consumer<? super T> consumer)。其一般与ofNullable()搭配使用,因为of()在创建时已经完成了判断,而empty()只是单纯了实例化了一个Optional对象。

// 使用实例
public class PresentDemo {

    public static void getStudentName(Student student){

        Optional<Student> optional = Optional.ofNullable(student);

        if (optional.isPresent()){
            //student不为null
            Student student1 = optional.get();
            System.out.println(student1);
        }else {
            System.out.println("student为null");
        }

        optional.ifPresent(s-> System.out.println(s));
    }

    public static void main(String[] args) {

        Student student = new Student(1,"zhangsan","M");
        getStudentName(student);
    }
}
// 源码分析
// isPresent()内部非常简单,就是判断这个值是否为null。
public boolean isPresent() {
    return value != null;
}

public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

ifPresent()方法在执行时,接收一个consumer函数式接口,如果value不为null,则通过consumer中的accept方法获取该值。

3、get()应用&源码解析

get()的使用非常简单,但不安全,因为其在获取值的时候,如果值存在,则直接返回封装在Optional中的值,如果不存在,则抛出NoSuchElementException。因此它的使用前提是已经确定Optional中有值,否则没有使用意义。

// 使用实例
Optional<Student> studentOptional = Optional.ofNullable(student);
if (studentOptional.isPresent()){
	Student result = studentOptional.get();
}
// 源码分析
public Tget() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

4、orElseThrow()应用&源码解析

该方法与get()类似,都是用于取值,但是当Optional中没有值时,get()会直接抛出NoSuchElementException,这样的话,就存在了一定的局限性,因为有时可能需要抛出自定义异常。此时就可以使用orElseThrow(),它在取值时,如果Optional中没有值时,可以抛出自定义异常。

// 使用实例
Optional<Student> optional = Optional.ofNullable(student);

try {
	// null 就抛异常
    Student result = optional.orElseThrow(MyException::new);
    System.out.println(result);
} catch (MyException e) {
    e.printStackTrace();
}
// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else { // null 的话就抛异常
        throw exceptionSupplier.get();
    }
}

5、map()应用&源码解析

map()可以实现类型转换,与JDK8的Stream的map类似,只不过一个是转换Stream的泛型,一个是转换Optional的泛型。

// 使用案例
if (studentOptional.isPresent()){
	// Student类型转为String类型
	Optional<String> nameOptional = studentOptional.map(Student::getName);
}
// 源码分析
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));
    }
}

6、flatMap()应用&源码解析

刚才已经通过map()获取了学生的姓名,操作非常简单。但是当产生链路获取时,map可以使用么? 如:学生->工作->公司->公司名称。

现在可能大家脑袋里已经有了一个想法,就是通过map(),代码结构如下:

studentOptional.map(Student::getJob).map(Job::getCompany).map(Company::getName);

但是这段代码是无法通过编译的。因为根据map的学习,每一次在调用的时候,都会对Optional的泛型进行改变,最终产生多层Optional嵌套的结构。
在这里插入图片描述

对于这个问题的解决,Optional类中提供了另外一个获取值的方法flatMap()。它本身用于多层调用,同时对于结果它不会形成多个Optional,而是将结果处理成最终的一个类型的Optional。但是通过flatMap获取的返回值必须是Optional类型。而map则没有这个限制。

// 使用实例
Optional<String> nameOptional = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName);
// 源码分析
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {// 多次调用不会生成Optional嵌套
        return Objects.requireNonNull(mapper.apply(value));
    }
}

7、filter()应用&源码解析

Optional类中提供了数据过滤的方法filter()来实现这个需求。其会根据传入的条件进行判断,如果匹配则返回一个Optional对象并包含对应的值,否则返回一个空值的Optional。

// 使用实例
Optional<Company> company = companyOptional.filter(c -> "bank".equals(c.getName()));

源码是传入一个Predicate,如果不为null的话,就会调用predicate的test方法做判断。

// 源码分析
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

8、orElse()应用&源码解析

在取值的时候,如果值不存在,有时我们会考虑返回一个默认值。该需求就可以通过orElse()实现。

其内部会判断值是否为null,如果不为null,则返回该值,如果为null,则返回传入的默认值。

// 使用案例
String value = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName).orElse("default value");
// 源码分析
publicT orElse(T other) {
	// null的话取默认值
    return value != null ? value : other;
}

9、orElseGet()应用&源码解析

orElseGet()也是用于当Optional中没有值时,返回默认值的方法。但是它与orElse()的区别在于,它是延迟加载的。只有当Optional中没有值是才会被调用。

// 源码分析
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}
// 代码案例
System.out.println(Optional.ofNullable("student").orElse(getStr("a")));
System.out.println(Optional.ofNullable(null).orElse(getStr("b")));
System.out.println(Optional.ofNullable("student").orElseGet(() ->getStr("a")));
System.out.println(Optional.ofNullable(null).orElseGet(() ->getStr("b")));

我们发现, 当数据不为null的时候,orElseGet不会执行。

所以在使用时,更加推荐使用orElseGet(),因为它使用延迟调用所以性能更加优异。

10、orElseThrow()应用&源码分析

orElseThrow很简单,如果为null的话,就抛一个异常。

// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/829242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

string【2】模拟实现string类(超详解哦)

string模拟实现 引言&#xff08;实现概述&#xff09;string类方法实现默认成员函数构造函数拷贝构造赋值运算符重载析构函数 迭代器beginend 容量size、capacity、emptyreserveresize 访问元素operator[] 修改insert插入字符插入字符串 appendpush_backoperatoreraseclearswa…

BGP----边界网关路协议

目录 一&#xff0c;BGP相关的特点 二、BGP特点&#xff1a; 三、BGP数据包 四、BGP的工作过程 五、名词 六、BGP的路由黑洞问题 七、BGP的防环机制 –水平分割 八、BGP的基本配置&#xff1a; 1.直连的EBGP邻居关系建立 2.IBGP邻居关系建立 3.EBGP邻居间存在多条…

【NetCore】06-配置框架

文章目录 1.配置框架1.1 核心包1.2 配置框架核心类型1.3 配置框架扩展点 2.命令行配置提供程序2.1 支持的命令格式2.2 命令替换模式 3. 环境变量配置提供程序3.1 使用场景3.2 特性 4.文件配置提供程序4.1 文件配置提供程序4.2 特性 5.配置变更监听-配置热更新能力的核心5.1 场景…

客服如何通过微信接收消息通知-唯一客服文档中心

当我们在自己网站上嵌入对接了客服代码&#xff0c;我们想要通过微信接收访客的消息提醒通知&#xff0c;可以通过扫描客服后台的微信二维码&#xff0c;即时收消息通知提醒。 我们网站地址&#xff1a;gofly.v1kf.com 客服后台 后台主页面板&#xff0c;就展示了一个微信二维码…

PHP http请求封装使用(POST.GET,PUT,DELETE)

封装的 sendRequest() 函数是一个通用的发送请求函数&#xff0c;可以发送 POST、GET、PUT、DELETE 请求。下面对该函数的代码进行具体讲解&#xff1a; <?php function sendRequest($method, $url, $data null, $contentType multipart/form-data, $headers array(),…

无人驾驶实战-第一课(自动驾驶概述)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 ————————————————————————————————————————— 无人驾驶汽车的定义&#xff1a; 无人驾驶汽车是可载人的移动智能机器…

【ASP.NET MVC】动态与静态网站(3)

一、区别 静态网页&#xff08;站&#xff09; 用户通过浏览器提交访问需求&#xff0c;需求可以是默认首页或者指定的网站中的某个页面&#xff0c;WEB服务器查找对应的网页&#xff0c;通过HTTP协议发送到客户端&#xff0c;完成访问。 特点&#xff1a;每次访问、不同角色…

视频监控综合管理平台EasyCVR修改参数提示database or disk is full是什么原因?

EasyDarwin开源流媒体视频EasyCVR安防监控平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析等能力。视频监控综合管理平台EasyCVR具备视频汇聚融合能力&#xff0c;平台基于云边端一体化架构&#xff0c;具有强大…

【MCU学习】GD32F427VG开发

&#xff08;一&#xff09;学习文档和例程 兆易创新GD32 MCU参考资料下载 1.GD232F4xx的Keil芯片支持包 2.标准固件库和示例程序 3.GD32F4xx_固件库使用指南_Rev1.2 4.用户手册&#xff1a;GD32F4xx_User_Manual_Rev2.8_CN 5.数据手册&#xff1a;GD32F427xx_Datasheet_Rev…

通信笔记——最小移频键控MSK

由2FSK→MSK存在以下几点&#xff1a; 1、如何实现已调信号的码元正交&#xff1b;2、如何实现相位连续&#xff0c;不突变。 1、以2FSK一般表示法出发&#xff0c;推导得出两种频率的约束关系 正交条件&#xff1a; 积化和差公式有&#xff1a; 当载波频率比较高&#xff…

类欧几里得算法学习笔记

偶然发现了学长发给我的一个学长的学长也是我的学长的一个数论 p p t ppt ppt&#xff0c;先不着急复习莫反杜教筛&#xff0c;按这个顺序来吧 0.随便说说 前一阵子确实学习状态不是很好&#xff0c;我感觉我个人学习状态也是忽好忽坏的&#xff0c;不过只要在学习状态好的时…

大麦链接源码 大麦一键生成订单截图

8.4最新版源码 更新了大麦链接模版 更新了大麦订单截图一键生成 下载源码&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

TM4C123的ROM函数和非ROM函数区别

前言 在开发TM4C123的时候&#xff0c;我看到ROM函数和非ROM函数。例如ROM_FPUEnable()和FPUEnable()函数&#xff0c;这两个就只是前缀不一样。有什么区别和不同&#xff1f; 相同点 ROM函数和非ROM函数的作用起始是一样的&#xff0c;比如上面的例子ROM_FPUEnable()和FPUEnab…

如何有效地扩展数据库服务器以满足日益增长的工作量

在当今以数据为驱动的世界中&#xff0c;企业面临着一个挑战&#xff0c;即在保证应用程序的最佳性能的同时&#xff0c;管理迅速增长的数据量。扩展数据库服务器在满足这些需求方面起着至关重要的作用。本篇博客将探讨各种策略&#xff0c;以有效地扩展数据库服务器&#xff0…

[C++]

C 一.C基础入门1.HelloWorld2.注释3.变量4.常量5.关键字6.命名规则 二.数据类型1.整形2.sizeof关键字3.浮点型4.字符型5.转义字符6.字符串型7.布尔类型8.数据的输入 三.运算符1.算数运算符2.赋值运算符3.比较运算符4.逻辑运算符 一.C基础入门 1.HelloWorld 首先到官网下载并安…

P2824 [HEOI2016/TJOI2016] 排序(线段树)(内附封面)

[HEOI2016/TJOI2016] 排序 题目描述 在 2016 2016 2016 年&#xff0c;佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题&#xff0c;现在她在研究一个难题&#xff0c;需要你来帮助她。 这个难题是这样子的&#xff1a;给出一个 1 1 1 到 n n n 的…

关于单测技术选型,聊聊我的思考

对于单测来说&#xff0c;目前常用的单测框架有&#xff1a; JUnitMockitoSpockPowerMockJMockitTestableMock 其中 JUnit 不支持 Mock&#xff0c;因此基本不会只用 JUnit&#xff0c;而是结合其他有 Mock 功能的框架一起使用。从知名度及使用率来说&#xff0c;Mockito 和 …

【基于HBase和ElasticSearch构建大数据实时检索项目】

基于HBase和ElasticSearch构建大数据实时检索项目 一、项目说明二、环境搭建三、编写程序四、测试流程 一、项目说明 利用HBase存储海量数据&#xff0c;解决海量数据存储和实时更新查询的问题&#xff1b;利用ElasticSearch作为HBase索引&#xff0c;加快大数据集中实时查询数…

干就完了

&#xff08;1&#xff09; ENIAC诞生于1946年。但安达信在1954年就拿计算机给通用电气公司算薪&#xff0c;这算计算机最早在工商业界的应用。 其实算薪这个事吧&#xff0c;严格意义来说是人力服务的BPO&#xff0c;只不过为了让这个算薪BPO项目更高效率更高质量更低成本地完…

无人机巢的作用及应用领域解析

无人机巢作为无人机领域的创新设备&#xff0c;不仅可以实现无人机的自主充电和电池交换&#xff0c;还为无人机提供安全便捷的存放空间。为了帮助大家更好地了解无人机巢&#xff0c;本文将着重解析无人机巢的作用和应用领域。 一、无人机巢的作用 无人机巢作为无人机技术的重…