Java 8中Optional类

2024-08-29 18:20
文章标签 java optional

本文主要是介绍Java 8中Optional类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 引言
    • 概述
    • 创建Optional对象
      • Optional.of
      • Optional.ofNullable
      • Optional.empty
    • 操作Optional对象
      • 检查值是否存在:isPresent与isEmpty
      • 获取值:get方法
      • 获取值(提供默认值):orElse、orElseGet、orElseThrow
      • 消费值:ifPresent与ifPresentOrElse
    • Optional高级操作
      • 转换与映射:map与flatMap
      • 过滤值:filter
      • 链式调用与组合操作
      • Optional与流(Stream)的结合使用
    • 最佳实践
      • Optional作为返回类型的使用场景
      • 避免将Optional用于成员变量
      • Optional在方法参数中的使用注意事项
      • 与传统null处理的对比与替代
      • 过渡使用Optional和反模式
      • 性能开销
      • 不适合序列化
      • 不适合作为集合类型
      • Optional作为方法参数的局限性
      • 与现有代码的兼容性问题
      • 不适合所有场景
    • 总结

引言

在Java项目开发中,null值的处理一直是开发者的鸡肋。null引发的空指针异常在许多项目中都是常见的错误。为了更好地处理可能为空的对象,并减少null值带来的风险,Java 8引入了Optional类。

Optional类的设计目标是为可能为空的值提供一种明确的表达方式,而不是简单地返回null。通过使用Optional,开发者可以更清晰地表达代码的意图,即某个值可能存在,也可能不存在。这也是引入Optional的原因,详细请看一下官网文章。

概述

Optional是Java 8引入的一个容器类,用来表示可能存在也可能不存在的值。它为解决空指针异常提供了一种更优雅的解决方案。与直接使用null不同,Optional明确地表示一个值的存在或缺失,避免了因意外的null值引发的NullPointerException

Optional本质上是一个容器对象,它可能包含一个非空值,也可能为空。当值存在时,Optional会持有该值;当值不存在时,Optional会被认为是“空”的,这种明确的表示方式使得代码更易于理解和维护。

Optional的常见用例包括但不限于以下几种场景:

  • 方法返回值:当方法的返回值可能为空时,可以返回一个Optional对象,以明确表示结果的可能缺失。
  • 集合操作:在处理集合类数据时,查找、过滤等操作可能不会返回结果,这种情况下可以使用Optional来包装结果,避免null值的使用。
  • 防止空指针异常:在处理外部API或不受控制的代码时,Optional可以作为一种防御性编程手段,防止空指针异常的发生。
  • 链式操作Optional支持链式调用,通过map()flatMap()filter()等方法,可以优雅地处理复杂的数据转换和过滤逻辑。

创建Optional对象

Java中的Optional类有of()ofNullable()empty()三种方法来创建Optional对象,适用于不同的使用场景。

方法描述
Optional.of()适用于确定不会为空的值。若值可能为null,不应使用此方法,因为它会抛出异常。
Optional.ofNullable()适用于值可能为空的情况。这个方法是处理null值的安全方式,推荐在不确定值是否为空时使用。
Optional.empty()当需要显式地表示一个空值时使用。这个方法可以用于返回方法的默认空结果,避免使用null

Optional.of

Optional.of()方法用于创建一个包含非空值的Optional对象。如果传入的值为null,该方法会抛出NullPointerException。因此,这个方法主要适用于那些开发者能够保证不会为null的情况下。

Optional<String> optionalName = Optional.of("张无忌");

Optional.ofNullable

Optional.ofNullable()方法用于创建一个可能包含null值的Optional对象。这个方法的不同之处在于,它接受null作为参数,并会根据传入值的情况返回一个空的Optional或包含值的Optional

Optional<String> optionalName = Optional.ofNullable(null);

Optional.empty

Optional.empty()方法用于创建一个明确表示为空的Optional对象。这个方法通常在需要返回一个空Optional而不是null的情况下使用,以避免可能的null引用问题。

Optional<String> emptyOptional = Optional.empty();

操作Optional对象

在创建Optional对象之后,Java提供了一系列的方法来检查、获取和操作其中的值。通过这些方法,开发者可以优雅地处理可能为空的情况,避免空指针异常,并编写更加安全和易读的代码。

检查值是否存在:isPresent与isEmpty

Optional类提供了两个方法来检查值的存在性:isPresent()isEmpty()

方法描述
isPresent()如果Optional包含一个非空值,返回true。否则返回false
isPresent()如果Optional为空,返回true。否则返回falseisEmpty()Java 11新增的方法。
Optional<String> optionalName = Optional.of("张无忌");if (optionalName.isPresent()) {System.out.println("名字为:" + optionalName.get());
} else {System.out.println("名字为空");
}
// 输出:名字为:张无忌

获取值:get方法

get()方法用于从Optional对象中获取包含的值。如果Optional为空,调用get()将抛出NoSuchElementException异常。因此,在调用get()之前,通常会使用isPresent()isEmpty()进行检查。

Optional<String> optionalName = Optional.of("东方不败");String name = optionalName.get();
System.out.println("名字为: " + name);
// 输出:名字为:东方不败

虽然get()方法直接获取值,但它的使用需要谨慎,最好在确定Optional不为空的情况下使用,以避免潜在的异常。

获取值(提供默认值):orElse、orElseGet、orElseThrow

Optional提供了三种方法来处理可能为空的情况,分别是orElse()orElseGet()orElseThrow()

方法描述
orElse()如果Optional中有值,则返回该值;如果为空,则返回other作为默认值。
orElseGet()orElse()类似,但other是一个Supplier接口的实现,可以通过惰性求值在需要时计算默认值。
orElseThrow()如果Optional中有值,则返回该值;如果为空,则抛出一个NoSuchElementException异常。orElseThrow()也有一个带有Supplier参数的重载版本,可以自定义抛出的异常。
Optional<String> optionalName = Optional.ofNullable(null);// 使用orElse提供默认值
String name1 = optionalName.orElse("张三");
System.out.println("名字为: " + name1); // 名字为: 张三// 使用orElseGet提供默认值
String name2 = optionalName.orElseGet(() -> "李四");
System.out.println("名字为: " + name2); // 名字为: 李四// 使用orElseThrow抛出异常
try {String name3 = optionalName.orElseThrow(() -> new NoSuchElementException("值为null"));System.out.println("名字为:" + name3); // 不会执行
} catch (NoSuchElementException e) {System.out.println("Name为null,引发异常。");
}
// 输出:Name为null,引发异常。

消费值:ifPresent与ifPresentOrElse

方法描述
ifPresent()如果Optional中有值,则对该值执行指定的操作(Consumer接口的实现);如果为空,不执行任何操作。
ifPresentOrElse()如果Optional中有值,则对该值执行指定的操作;如果为空,则执行另一个操作(Runnable接口的实现)。ifPresentOrElse()Java 9新增的方法。
Optional<String> optionalName = Optional.of("张无忌");// 使用ifPresent消费值
optionalName.ifPresent(name -> System.out.println("名字为: " + name));
// 输出:名字为: 张无忌// 使用ifPresentOrElse处理存在和不存在的情况
optionalName.ifPresentOrElse(name -> System.out.println("名字为: " + name),() -> System.out.println("名字为空")
);
// 输出:名字为: 张无忌

Optional高级操作

Optional不仅提供了基础的检查和获取值的方法,还包含了一些更为高级的操作。这些操作允许开发者以更加简洁和优雅的方式处理复杂的逻辑,包括值的转换、过滤和与其他Optional的组合操作。map()flatMap()filter()等方法,以及Optional与流(Stream)的结合使用。

转换与映射:map与flatMap

Optional提供的map()flatMap()方法可以用来转换和映射包含的值,使得对值的操作更加灵活和链式化。

方法描述
map()map()方法接收一个函数作为参数,并将该函数应用于Optional中的值,如果Optional为空,则返回一个空的Optional。该方法通常用于将Optional中的值转换为另一种类型的值。
flatMap()map()类似,但flatMap()要求映射函数返回一个Optional对象,并直接将其结果作为flatMap()的返回值。这对于需要返回嵌套的Optional或处理复杂链式操作非常有用。
// 使用map转换值
Optional<String> optionalName1 = Optional.of("张无忌");// Optional<Integer> nameLength = optionalName.map(String::length);
Optional<Integer> nameLength = optionalName1.map((name) -> name.length());
System.out.println("名字长度为:" + nameLength.orElse(0));
// 输出:名字长度为:3// 使用flatMap链式转换
Optional<String> optionalName2 = Optional.of("zhangwuji");Optional<String> upperCaseName = optionalName2.flatMap(name -> Optional.of(name.toUpperCase()));
System.out.println("大写的名字为: " + upperCaseName.orElse("UNKNOWN"));
// 输出:ZHANGWUJI

过滤值:filter

filter()方法用于根据给定的条件对Optional中的值进行过滤。如果值满足条件,则返回包含该值的Optional;如果不满足条件,或者Optional为空,则返回一个空的Optional

Optional<String> optionalName = Optional.of("张无忌");Optional<String> name = optionalName.filter(n -> n.length() == 2);
System.out.println("名字为: " + name.orElse("名字不是2两个字的"));
// 输出:名字不是2两个字的

在这个示例中,filter()方法根据名称的长度来筛选值,只有当名称长度大于3时,Optional才会包含值,否则将返回一个空的Optional

链式调用与组合操作

Optional的方法支持链式调用,这使得对值的操作更加简洁和连贯。通过将map()flatMap()filter()等方法结合使用,开发者可以一步步地处理复杂的逻辑,而无需过多的中间变量或嵌套的if语句。

Optional<String> optionalName = Optional.of(" vernin  ");Optional<String> name = optionalName.map(String::trim).filter(n -> n.length() > 3).map(String::toUpperCase);System.out.println("格式化后的名字为:" + name.orElse("无效名字"));
// 输出:格式化后的名字为:VERNIN

Optional与流(Stream)的结合使用

OptionalStream在Java 8中引入,二者可以很好地结合使用。Optional中的stream()方法允许将其转换为一个包含零或一个元素的Stream,从而可以与其他流操作结合使用。

示例代码

List<Optional<String>> names = Arrays.asList(Optional.of("张无忌"),Optional.empty(),Optional.of("东方不败")
);// 将Optional转为Stream并过滤非空值
List<Optional<String>> noNullNames = names.stream().filter(Optional::isPresent).collect(Collectors.toList());noNullNames.forEach(v -> System.out.print(v.get()));
// 输出:张无忌 东方不败

最佳实践

Optional是Java 8引入的强大工具,用于处理可能为空的值。虽然Optional可以有效减少空指针异常的发生,但在实际开发中,合理使用Optional至关重要。以下是一些关于Optional的最佳实践,帮助开发者在项目中更好地利用这一工具。

Optional作为返回类型的使用场景

Optional最常见的使用场景是作为方法的返回类型,特别是当方法可能返回null时。通过返回Optional,方法调用者明确知道返回值可能为空,从而迫使调用者处理这一情况,减少NullPointerException的风险。

public Optional<User> findUserById(String userId) {// 如果找到用户,返回Optional.of(user)// 如果没有找到用户,返回Optional.empty()return userRepository.findById(userId);
}

注意

  • 在公共API中返回Optional是一个好习惯,因为它明确表达了值可能为空的可能性。
  • 避免将Optional作为集合类型的返回值,例如Optional<List<T>>,因为空集合和空Optional的语义是不同的,前者意味着没有元素,后者意味着没有结果。

避免将Optional用于成员变量

虽然Optional适合作为方法的返回类型,但通常不推荐将Optional用于类的成员变量。这是因为Optional是一个相对重量级的对象,并且它的存在增加了代码的复杂性和不必要的包装。

// 不推荐的做法
public class User {private Optional<String> middleName; // 避免使用Optional作为成员变量// 其他字段和方法
}// 推荐的做法
public class User {private String middleName; // 直接使用String,并在使用时检查null// 其他字段和方法
}

理由

  • 使用Optional作为成员变量可能会导致额外的内存开销和性能损失。
  • 类的设计应尽量保持简单,成员变量应直接表达其含义。对于可能为空的值,开发者可以通过getter方法返回Optional,而不是将Optional直接作为成员变量。

Optional在方法参数中的使用注意事项

Optional不应作为方法参数使用,因为这会使API变得复杂且不直观。相比之下,直接传递可能为空的值,并在方法内部处理null,更为合适。

// 不推荐的做法
public void processUser(Optional<User> user) {// 处理逻辑
}// 推荐的做法
public void processUser(User user) {if (user != null) {// 处理非空用户} else {// 处理用户为空的情况}
}

理由

  • Optional作为参数可能会导致不必要的嵌套,并增加代码的复杂性。
  • 传递null并在方法内部进行处理,或设计方法重载(例如提供带有默认值的重载),通常是更好的选择。

与传统null处理的对比与替代

Optional提供了一种替代null的方式,但并不是所有情况下都应使用Optional。在某些简单场景下,直接使用null并进行null检查,可能更加高效和直观。

// 使用Optional处理可能为null的返回值
Optional<String> result = computeValue();
result.ifPresent(value -> System.out.println("名字为: " + value));// 传统的null处理方式
String result = computeValue();
if (result != null) {System.out.println("名字为: " + result);
}

注意

  • 在简单且可控的场景下,传统的null检查可能更为适合,尤其是在对性能有较高要求的场景中。
  • Optional的主要优势在于它的显式性和流式API,但开发者应根据具体情况权衡使用Optional和传统null处理方式的利弊。

过渡使用Optional和反模式

尽管Optional是一个有用的,但在使用时需要避免滥用和反模式。一些常见的反模式包括:

  • 嵌套Optional:例如Optional<Optional<T>>,这种结构使代码复杂化,应尽量避免。
  • 强制Optional存在:如果在大多数情况下,Optional总是存在值,那么使用Optional就失去了意义。应评估是否需要使用Optional
  • 过度使用Optional:在不必要的场合使用Optional,例如在性能关键的路径中,可能会导致不必要的开销。

// 反模式示例:嵌套Optional
Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("张无忌"));// 过度使用Optional示例
Optional<String> optionalValue = Optional.ofNullable("东方不败"); // 直接使用常量值即可

性能开销

Optional本质上是一个封装对象,因此在某些情况下可能会带来额外的性能开销。特别是在性能敏感的代码路径中,频繁创建和处理Optional对象可能会导致不必要的对象分配和垃圾回收压力。

示例

// 使用Optional可能导致频繁的对象创建
Optional<Integer> optionalValue = Optional.of(42);

分析

  • Optional的使用会导致额外的对象包装,而在高性能要求的场景中,这种开销可能是不可接受的。
  • 对于简单的null检查,如果性能是关键因素,传统的null处理方式可能更高效。

不适合序列化

Optional通常不建议用于对象的持久化或序列化。这是因为Optional主要是为了减少null检查的复杂性,而不是作为数据持久化的手段。

public class User implements Serializable {private Optional<String> middleName; // 不建议序列化Optional
}

分析

  • Optional的设计初衷是用于临时性的数据封装,而不是长期持久化的数据模型。序列化Optional会导致不必要的复杂性,并且在某些情况下可能会导致意外的反序列化问题。
  • 在持久化领域,直接使用原始类型(如String)并处理null值更加合适。

不适合作为集合类型

Optional不应作为集合的元素或嵌套在集合类型中使用。例如,Optional<List<T>>List<Optional<T>>等结构通常是不推荐的。这会导致代码复杂性增加,并且可能会引入不必要的嵌套。

// 不推荐的做法,可能导致复杂的嵌套逻辑
List<Optional<String>> optionalList = Arrays.asList(Optional.of("张无忌"), Optional.empty());

分析

  • Optional设计为一个简单的包装器,用于单个值的处理。当与集合类型结合使用时,会产生多层嵌套,导致代码难以阅读和维护。
  • 更好的做法是将Optional用于处理单个值,并直接使用集合来管理多个元素,而不是将Optional与集合类型混用。

Optional作为方法参数的局限性

正如前面在最佳实践部分提到的,Optional不适合作为方法参数。这不仅会使API复杂化,还会降低代码的可读性和使用便捷性。

示例

// 不推荐的做法:Optional作为方法参数
public void process(Optional<String> name) {name.ifPresent(System.out::println);
}

分析

  • Optional作为方法参数会让调用方在传参时感到困惑,并增加不必要的封装成本。
  • 直接传递可能为null的值,并在方法内部处理null情况,通常更加清晰和直接。

与现有代码的兼容性问题

在老旧代码或不支持Optional的环境中,强行引入Optional可能会导致兼容性问题。这尤其在与不使用Optional的库或框架进行集成时表现明显。

示例

// 旧版API可能不支持Optional
public String getUserNameById(String userId) {// 返回String而不是Optional<String>
}

分析

  • 在与老旧代码库集成时,使用Optional可能会引入不必要的复杂性,要求额外的包装和拆包步骤。
  • 在这种情况下,最好保持API的一致性,使用传统的null处理方式。

不适合所有场景

Optional适合在明确需要处理可能为空的值时使用,但在某些场景下,它可能并不是最佳选择。例如,当确定值不会为空,或者处理空值的逻辑非常简单时,使用Optional反而会增加代码复杂性。

// 不适合的场景:确定不会为空的值
public Optional<Integer> calculateSum(int a, int b) {return Optional.of(a + b); // 这种情况下使用Optional没有意义
}

分析

  • 如果一个值确定不会为空,使用Optional就没有意义,反而增加了不必要的包装。
  • 开发者应根据实际需求判断是否需要使用Optional,避免为了使用而使用。

总结

Optional作为Java 8引入的重要特性,为开发者提供了一种优雅的方式来处理可能为空的值。通过使用Optional,我们可以减少空指针异常的风险,使代码更加简洁,并且更具安全性。然而,Optional并非万能,合理使用它至关重要,包括Stream也是。

在使用Optional时,以下几个关键点值得牢记:

  1. 明确使用场景Optional最适合作为方法的返回类型,用于表示可能为空的结果。当需要处理单一可能为空的值时,Optional是一个很好的选择。
  2. 避免滥用:不要将Optional用于成员变量或方法参数,这会增加代码的复杂性和性能开销。在设计API时,确保Optional的使用是必要的,而不是为了使用而使用。
  3. 选择合适的操作Optional提供了多种操作方法,如map()flatMap()filter()等,能够简化复杂的逻辑处理。在处理Optional时,尽量使用链式调用来保持代码的简洁性。
  4. 关注性能Optional封装了额外的对象,因此在性能敏感的场景中,需要权衡其引入的开销。对于一些简单的null检查,传统的处理方式可能更为高效。
  5. 理解局限性Optional并不适用于所有场景。在某些情况下,使用Optional反而可能增加代码的复杂性或引入性能问题。开发者应根据实际需求,选择最合适的工具和方法。

这篇关于Java 8中Optional类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118599

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定