当前位置: 首页 > news >正文

Java 8 Optional 详解

在 Java 8 中,引入了一个新的类——Optional,它提供了一种用于表示可选值而非空引用的类级别解决方案。作为一名 Java 程序员,我们经常会遇到 NullPointerException(NPE),尽管我们与它“熟得就像一位老朋友”,但每次遇到它,程序都会因为使用了一个值为 null 的对象而抛出异常。为了应对这种情况,我们通常会进行大量的 null 值检查,尽管有时候这种检查完全没有必要。Java 8 引入了 Optional 类,以便我们编写的代码不再那么刻薄呆板。

1 没有 Optional 会有什么问题

假设我们有一个实际的应用场景:小王第一天上班,领导老马让他从数据库中根据会员 ID 拉取一个会员的姓名,然后将姓名打印到控制台。小王很快写下了这段代码:

public class WithoutOptionalDemo {class Member {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}public static void main(String[] args) {Member mem = getMemberByIdFromDB();if (mem != null) {System.out.println(mem.getName());}}public static Member getMemberByIdFromDB() {// 当前 ID 的会员不存在return null;}
}

在这个例子中,getMemberByIdFromDB() 方法返回了 null,表示没有获取到该会员。因此,在打印会员姓名时,必须先对 mem 进行判空,否则会抛出 NullPointerException

2 Optional 是如何解决这个问题的

小王提交代码后,老马建议他尝试使用 Optional 来避免不必要的 null 值检查。下面是使用 Optional 改进后的代码:

import java.util.Optional;public class OptionalDemo {public static void main(String[] args) {Optional<Member> optional = getMemberByIdFromDB();optional.ifPresent(mem -> {System.out.println("会员姓名是:" + mem.getName());});}public static Optional<Member> getMemberByIdFromDB() {boolean hasName = true;if (hasName) {return Optional.of(new Member("沉默王二"));}return Optional.empty();}static class Member {private String name;public Member(String name) {this.name = name;}public String getName() {return name;}// getter / setter}
}

在这个例子中,getMemberByIdFromDB() 方法返回了 Optional<Member>,表示 Member 可能存在也可能不存在。通过 ifPresent() 方法,我们可以直接使用 Lambda 表达式来打印结果,而不需要进行显式的 null 检查。

3 创建 Optional 对象

创建 Optional 对象有三种方式:

  1. 使用 empty() 方法创建一个空的 Optional 对象

    Optional<String> empty = Optional.empty();
    System.out.println(empty); // 输出:Optional.empty
    
  2. 使用 of() 方法创建一个非空的 Optional 对象

    Optional<String> opt = Optional.of("沉默王二");
    System.out.println(opt); // 输出:Optional[沉默王二]
    

    注意,传递给 of() 方法的参数不能为 null,否则会抛出 NullPointerException

  3. 使用 ofNullable() 方法创建一个即可空又可非空的 Optional 对象

    String name = null;
    Optional<String> optOrNull = Optional.ofNullable(name);
    System.out.println(optOrNull); // 输出:Optional.empty
    

    ofNullable() 方法内部有一个三元表达式,如果参数为 null,则返回私有常量 EMPTY;否则使用 new 关键字创建一个新的 Optional 对象。

4 判断值是否存在

可以通过 isPresent() 方法判断一个 Optional 对象是否存在值:

Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 输出:trueOptional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); // 输出:false

在 Java 11 中,还可以使用 isEmpty() 方法判断与 isPresent() 相反的结果。

Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isEmpty()); // 输出:falseOptional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isEmpty()); // 输出:true

5 非空表达式

Optional 类提供了一个非常现代化的方法——ifPresent(),允许我们使用函数式编程的方式执行一些代码:

Optional<String> opt = Optional.of("沉默王二");
opt.ifPresent(str -> System.out.println(str.length()));

在 Java 9 中,还可以使用 ifPresentOrElse(action, emptyAction) 方法,在值存在时执行 action,在值不存在时执行 emptyAction

Optional<String> opt = Optional.of("沉默王二");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));

6 设置(获取)默认值

Optional 提供了 orElse()orElseGet() 方法来设置或获取默认值:

  • orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值:

    String nullName = null;
    String name = Optional.ofNullable(nullName).orElse("沉默王二");
    System.out.println(name); // 输出:沉默王二
    
  • orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数:

    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseGet(() -> "沉默王二");
    System.out.println(name); // 输出:沉默王二
    

7 获取值

get() 方法用于获取 Optional 对象的值,但如果值为 null,该方法会抛出 NoSuchElementException 异常:

public class GetOptionalDemo {public static void main(String[] args) {String name = null;Optional<String> optOrNull = Optional.ofNullable(name);System.out.println(optOrNull.get()); // 抛出 NoSuchElementException}
}

建议使用 orElseGet() 方法来获取 Optional 对象的值。

8 过滤值

Optional 提供了 filter() 方法,用于根据条件过滤值:

public class FilterOptionalDemo {public static void main(String[] args) {String password = "12345";Optional<String> opt = Optional.ofNullable(password);System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent()); // 输出:false}
}

在这个例子中,filter() 方法的参数是一个 Predicate,用于判断密码的长度是否大于 6。如果条件不满足,则返回一个空的 Optional 对象。

在上例中,由于 password 的长度为 5 ,所以程序输出的结果为 false。假设密码的长度要求在 6 到 10 位之间,那么还可以再追加一个条件。

Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);

这次程序输出的结果为 true,因为密码变成了 7 位,在 6 到 10 位之间。

9 转换值

Optional 提供了 map() 方法,用于按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象:

public class OptionalMapDemo {public static void main(String[] args) {String name = "沉默王二";Optional<String> nameOptional = Optional.of(name);Optional<Integer> intOpt = nameOptional.map(String::length);System.out.println(intOpt.orElse(0)); // 输出:4}
}

在这个例子中,map() 方法将字符串类型的 Optional 对象转换为整数类型的 Optional 对象,表示字符串的长度。

public class OptionalMapFilterDemo {public static void main(String[] args) {String password = "password";Optional<String>  opt = Optional.ofNullable(password);Predicate<String> len6 = pwd -> pwd.length() > 6;Predicate<String> len10 = pwd -> pwd.length() < 10;Predicate<String> eq = pwd -> pwd.equals("password");boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();System.out.println(result);}
}

上例中把 map() 方法与 filter() 方法结合起来用,前者用于将密码转化为小写,后者用于判断长度以及是否是“password”

10 总结

Optional 类是 Java 8 引入的一个重要特性,它提供了一种优雅的方式来处理可能为 null 的值,避免了大量的 null 值检查,从而提高了代码的可读性和安全性。通过 Optional,我们可以更清晰地表达程序的意图,减少 NullPointerException 的发生。掌握 Optional 的使用,对于现代 Java 开发来说是非常重要的一项技能。

11 思维导图

在这里插入图片描述

12 参考链接

Java 8 Optional最佳指南,优雅解决空指针


http://www.mrgr.cn/news/68373.html

相关文章:

  • 多个服务器共享同一个Redis Cluster集群,并且可以使用Redisson分布式锁
  • 开发工具 IntelliJ IDEA 使用技巧、快捷键、插件分享
  • 企业级-实现Redis封装层
  • 【设计模式系列】外观模式(十四)
  • Java基础使用①Java特点+环境安装+IDEA使用
  • 讲解JVM日志的查看及解决系统频繁GC问题
  • 简化招标流程:电子招标软件的关键作用
  • vue搭建项目之后的步骤操作
  • 科研——统计 Markdown 字符数量的插件
  • 贝塞尔曲线的超集即对应的数学模型
  • API返回值:代码界的“快递包裹”说明
  • 旅游社交小程序ssm+论文源码调试讲解
  • 深入理解封装与接口:Java程序设计的核心思想与最佳实践
  • C#中日期和时间的处理
  • java开发程序员职业发展的一些思考
  • 【华为机试题】 [Python] 光伏场地建设规划
  • 【算法设计与分析】期末复习
  • ElasticSearch 添加IK分词器
  • 大型语言模型(LLMs)关键技术指南
  • C加加中的结构化绑定(解包,折叠展开)
  • 国标GB28181公网直播EasyGBS国标GB28181设备管理软件支持的监控设备类型
  • 【C++】哈希表封装 unordered_map 和 unordered_set 的实现过程
  • 如何通过执行计划分析优化SQL查询性能——以`TrainOrderChange`表查询为例
  • UE5.4 PCG 复制关卡实例
  • go中的类型断言详解
  • 动态规划28:376. 摆动序列