百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

知识总结-Java开发指Optional详解

bigegpt 2025-03-02 16:14 6 浏览

一、简介

Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致NPE问题。


public static String getName(Student student)
{
     if(null == student){
          return "Unkown";
     }
     return student.getName();    
}

这是一个获取学生姓名的方法,方法入参为一个Student对象,为了防止student对象为null, 做了防御性检查:如果值为null,返回"Unkown"。

再看使用Optional优化后的方法:


public static String getName(Student student)
{
    return Optional.ofNullable(student).map(u -> u.getName()).orElse("Unkown");      
}

可以看到,Optional类结合lambda表达式的使用能够让开发出的代码更简洁和优雅。

二、Optional对象的创建

我们看下Optional类的部分源码:

    private static final Optional EMPTY = new Optional<>();
    private final T value;
    private Optional() {
        this.value = null;
    }
    public static Optional empty() {
        @SuppressWarnings("unchecked")
        Optional t = (Optional) EMPTY;
        return t;
    }
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
    public static  Optional of(T value) {
        return new Optional<>(value);
    }
    public static  Optional ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

可以看出,Optional类的两个构造方法都是private型的,因此类外部不能显示的使用new Optional()的方式来创建Optional对象,但是Optional类提供了三个静态方法empty()、of(T value)、ofNullable(T value)来创建Optinal对象,示例如下:


// 1、创建一个包装对象值为空的Optional对象
Optional strOp = Optional.empty();
// 2、创建包装对象值非空的Optional对象
Optional strOp1 = Optional.of("optional");
// 3、创建包装对象值允许为空的Optional对象
Optional strOp2 = Optional.ofNullable(null);

三、Optional 类典型接口的使用

下面以一些典型场景为例,列出Optional API常用接口的用法,并附上相应代码。

3.1 get()方法

简单看下get()方法的源码:

public T get() {
   if (value == null) {
      throw new NoSuchElementException("No value present");
   }
   return value;
}

可以看到,get()方法主要用于返回包装对象的实际值,但是如果包装对象值为null,会抛出NoSuchElementException异常。

3.2 isPresent()方法

isPresent()方法的源码:

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

可以看到,isPresent()方法用于判断包装对象的值是否非空。下面我们来看一段糟糕的代码:

public static String getName(Student student)
    {
        Optional stuOpt =  Optional.ofNullable(student);
        if(stuOpt.isPresent())
        {
            return stuOpt.get().getGender();
        }
        
        return "Unkown";
    }

这段代码实现的是第一章(简介)中的逻辑,但是这种用法不但没有减少null的防御性检查,而且增加了Optional包装的过程,违背了Optional设计的初衷,因此开发中要避免这种糟糕的使用~

3.3 ifPresent()方法

ifPresent()方法的源码:

public void ifPresent(Consumer consumer) {
    if (value != null)
       consumer.accept(value);
}

ifPresent()方法接受一个Consumer对象(消费函数),如果包装对象的值非空,运行Consumer对象的accept()方法。示例如下:

public static void printName(Student student){
   Optional.ofNullable(student).ifPresent(u ->  System.out.println("The student name is : " + u.getName()));
}

上述示例用于打印学生姓名,由于ifPresent()方法内部做了null值检查,调用前无需担心NPE问题。

3.4 filter()方法

filter()方法的源码:

public Optional filter(Predicate predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
}

filter()方法接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象。举例如下:

public static void filterAge(Student student)
{
        Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u ->  System.out.println("The student age is more than 18."));
}

上述示例中,实现了年龄大于18的学生的筛选。

3.5 map()方法

map()方法的源码:

public Optional map(Function mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
}

map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)。举例如下:


public static Optional getAge(Student student){
   return Optional.ofNullable(student).map(u -> u.getAge()); 
}

上述代码中,先用ofNullable()方法构造一个Optional对象,然后用map()计算学生的年龄,返回Optional对象(如果student为null, 返回map()方法返回一个空的Optinal对象)。

3.6 flatMap()方法

flatMap()方法的源码:

public Optional flatMap(Function> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

跟map()方法不同的是,入参Function函数的返回值类型为Optional类型,而不是U类型,这样flatMap()能将一个二维的Optional对象映射成一个一维的对象。以3.5中示例功能为例,进行faltMap()改写如下:

public static Optional getAge(Student student){
   return Optional.ofNullable(student).flatMap(u -> Optional.ofNullable(u.getAge())); 
}
3.7 orElse()方法

orElse()方法的源码:

public T orElse(T other) {
   return value != null ? value : other;
}

orElse()方法功能比较简单,即如果包装对象值非空,返回包装对象值,否则返回入参other的值(默认值)。如第一章(简介)中提到的代码:

public static String getName(Student student){
    return Optional.ofNullable(student).map(u -> u.getName()).orElse("Unkown");
}
3.7 orElseGet()方法

orElseGet()方法的源码:

public T orElseGet(Supplier other) {
    return value != null ? value : other.get();
}

orElseGet()方法与orElse()方法类似,区别在于orElseGet()方法的入参为一个Supplier对象,用Supplier对象的get()方法的返回值作为默认值。如:

public static String getGender(Student student){
   return Optional.ofNullable(student).map(u -> u.getName()).orElseGet(() -> "Unkown");      
}
3.8 orElseThrow()方法

orElseThrow()方法的源码:

public  T orElseThrow(Supplier exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

orElseThrow()方法其实与orElseGet()方法非常相似了,入参都是Supplier对象,只不过orElseThrow()的Supplier对象必须返回一个Throwable异常,并在orElseThrow()中将异常抛出:

public static String getName(Student student){
   return Optional.ofNullable(student).map(u -> u.getName()).orElseThrow(() -> new RuntimeException("Unkown"));      
}

orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。

四、注意事项

使用Optional开发时要注意正确使用Optional的“姿势”,特别注意不要使用3.2节提到的错误示范,谨慎使用isPresent()和get()方法,尽量多使用map()、filter()、orElse()等方法来发挥Optional的作用。

相关推荐

程序员请收好:10个非常有用的 Visual Studio Code 插件

一个插件列表,可以让你的程序员生活变得轻松许多。作者|Daan译者|Elle出品|CSDN(ID:CSDNnews)以下为译文:无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发人...

PADS在WIN10系统中菜单显示不全的解决方法

决定由AD转PADS,打开发现菜单显示不正常,如下图所示:这个是由于系统的默认字体不合适导致,修改一下系统默认字体即可,修改方法如下:打开开始菜单-->所有程序-->Windows系统--...

一文讲解Web前端开发基础环境配置

先从基本的HTML语言开始学习。一个网页的所有内容都是基于HTML,为了学好HTML,不使用任何集成工具,而用一个文本编辑器,直接从最简单的HTML开始编写HTML。先在网上下载notepad++文...

TCP/IP协议栈在Linux内核中的运行时序分析

本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看。1Linux概述  1.1Linux操作系统架构简介Linux操作系统总体上由Linux...

从 Angular Route 中提前获取数据

#头条创作挑战赛#介绍提前获取意味着在数据呈现在屏幕之前获取到数据。本文中,你将学到,在路由更改前怎么获取到数据。通过本文,你将学会使用resolver,在AngularApp中应用re...

边做游戏边划水: 基于浅水方程的水面交互、河道交互模拟方法

以下文章来源于腾讯游戏学堂,作者Byreave篇一:基于浅水方程的水面交互本文主要介绍一种基于浅水方程的水体交互算法,在基本保持水体交互效果的前提下,实现了一种极简的水面模拟和物体交互方法。真实感的...

Nacos介绍及使用

一、Nacos介绍Nacos是SpringCloudAlibaba架构中最重要的组件。Nacos是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供注册中心、配置中心和动态DNS...

Spring 中@Autowired,@Resource,@Inject 注解实现原理

使用案例前置条件:现在有一个Vehicle接口,它有两个实现类Bus和Car,现在还有一个类VehicleService需要注入一个Vehicle类型的Bean:publicinte...

一文带你搞懂Vue3 底层源码

作者:妹红大大转发链接:https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA前言vue3出来有一段时间了。今天正式开始记录一下梗vue3.0.0-be...

一线开发大牛带你深度解析探讨模板解释器,解释器的生成

解释器生成解释器的机器代码片段都是在TemplateInterpreterGenerator::generate_all()中生成的,下面将分小节详细展示该函数的具体细节,以及解释器某个组件的机器代码...

Nacos源码—9.Nacos升级gRPC分析五

大纲10.gRPC客户端初始化分析11.gRPC客户端的心跳机制(健康检查)12.gRPC服务端如何处理客户端的建立连接请求13.gRPC服务端如何映射各种请求与对应的Handler处理类14.gRP...

聊聊Spring AI的Tool Calling

序本文主要研究一下SpringAI的ToolCallingToolCallbackorg/springframework/ai/tool/ToolCallback.javapublicinter...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockers...

在MySQL登录时出现Access denied for user ~~ (using password: YES)

Windows~~~在MySQL登录时出现Accessdeniedforuser‘root‘@‘localhost‘(usingpassword:YES),并修改MySQL密码目录适用...

mysql 8.0多实例批量部署script

背景最近一个项目上,客户需要把阿里云的rdsformysql数据库同步至线下,用作数据的灾备,需要在线下的服务器上部署mysql8.0多实例,为了加快部署的速度,写了一个脚本。解决方案#!/bi...