《Java8实战》第11章 用 Optional 取代 null

这篇具有很好参考价值的文章主要介绍了《Java8实战》第11章 用 Optional 取代 null。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

11.1 如何为缺失的值建模

public String getCarInsuranceName(Person person) { 
 return person.getCar().getInsurance().getName(); 
} 

上面的这种代码就很容易出现NullPointerException的异常。

11.1.1 采用防御式检查减少 NullPointerException

为了避免NullPointerException异常,一般就会加很多判断。

public String getCarInsuranceName(Person person) { 
	if (person != null) { 
     Car car = person.getCar(); 
     if (car != null) { 
     		Insurance insurance = car.getInsurance(); 
         if (insurance != null) { 
         		return insurance.getName(); 
         } 
     } 
 } 
 return "Unknown"; 
} 


或者
public String getCarInsuranceName(Person person) { 
 if (person == null) { 
   return "Unknown"; 
 } 
 Car car = person.getCar(); 
 if (car == null) { 
   return "Unknown"; 
 } 
 Insurance insurance = car.getInsurance(); 
 if (insurance == null) { 
   return "Unknown"; 
 } 
 return insurance.getName(); 
} 

这种每次引用一次变量都做一次null的检查。

11.1.2 null 带来的种种问题
  • 它是错误之源。

NullPointerException 是目前 Java 程序开发中最典型的异常。

  • 它会使你的代码膨胀。

它让你的代码充斥着深度嵌套的 null 检查,代码的可读性糟糕透顶。

  • 它自身是毫无意义的。

null 自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模。

  • 它破坏了 Java 的哲学。

Java 一直试图避免让程序员意识到指针的存在,唯一的例外是:null 指针。

  • 它在 Java 的类型系统上开了个口子。

null 并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题

11.1.3 其他语言中 null 的替代品

比如 Groovy,通过引入安全导航操作符(safe navigation operator,标记为?)可以安全访问可能为 null 的变量。为了理解它是如何工作的,让我们看看下面这段 Groovy代码,它的功能是获取某个用户替他的汽车保险的保险公司的名称:
def carInsuranceName = person?.car?.insurance?.name
这段代码的表述相当清晰。person 对象可能没有 car 对象,你试图通过赋一个 null 给Person 对象的 car 引用,对这种可能性建模。类似地,car 也可能没有 insurance。Groovy的安全导航操作符能够避免在访问这些可能为 null 引用的变量时抛出 NullPointerException,在调用链中的变量遭遇 null 时将 null 引用沿着调用链传递下去,返回一个 null。
关于 Java 7 的讨论中曾经建议过一个类似的功能,不过后来又被舍弃了。

11.2 Optional 类入门

Java 8 中引入了一个新的类 java.util.Optional。这是一个封装 Optional 值的类。
使用 Optional 重新定义 Person/Car/Insurance 的数据模型

public class Person { 
 private Optional<Car> car; // 人可能有汽车,也可能没有汽车,因此将这个字段声明为 Optional
 public Optional<Car> getCar() { return car; } 
} 
public class Car { 
 private Optional<Insurance> insurance; // 汽车可能进行了保险,也可能没有保险,所以将这个字段声明为 Optional 
 public Optional<Insurance> getInsurance() { return insurance; } 
} 
public class Insurance { 
 private String name; // 保险公司必须有名字
 public String getName() { return name; } 
} 

在你的代码中始终如一地使用 Optional,能非常清晰地界定出变量值的缺失是结构上的问题,还是算法上的缺陷,抑或是数据中的问题。另外,我们还想特别强调,引入 Optional 类的意图并非要消除每一个 null 引用。与此相反,它的目标是帮助你更好地设计出普适的 API,让程序员看到方法签名,就能了解它是否接受一个 Optional 的值。这种强制会让你更积极地将变量从 Optional 中解包出来,直面缺失的变量值。

11.3 应用 Optional 的几种模式

11.3.1 创建 Optional 对象
  1. 声明一个空的 Optional

通过静态工厂方法 Optional.empty 创建一个空的 Optional 对象:
Optional<Car> optCar = Optional.empty();

  1. 依据一个非空值创建 Optional

还可以使用静态工厂方法 Optional.of 依据一个非空值创建一个 Optional 对象:
Optional<Car> optCar = Optional.of(car);
如果 car 是一个 null,这段代码就会立即抛出一个 NullPointerException,而不是等到你试图访问 car 的属性值时才返回一个错误。

  1. 可接受 null 的 Optional

Optional<Car> optCar = Optional.ofNullable(car);
如果 car 是 null,那么得到的 Optional 对象就是个空对象。

11.3.2 使用 map 从 Optional 对象中提取和转换值

你可能想要从 insurance 公司对象中提取公司的名称。提取名称之前,你需要检查 insurance 对象是否为 null
Optional 也提供了一个 map 方法。它的工作方式如下
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
《Java8实战》第11章 用 Optional 取代 null

11.3.3 使用 flatMap 链接 Optional 对象

利用 map 重写之前的代码

Optional<Person> optPerson = Optional.of(person); 
Optional<String> name = optPerson.map(Person::getCar) 
 .map(Car::getInsurance) 
 .map(Insurance::getName); 

但这段代码无法通过编译
optPerson 是 Optional类型的变量,调用 map 方法应该没有问题。但 getCar 返回的是一个 Optional类型的对象(如代码清单 11-4 所示),这意味着 map 操作的结果是一个 Optional<Optional>类型的对象。
《Java8实战》第11章 用 Optional 取代 null

  1. 使用 Optional 获取 car 的保险公司名称
public String getCarInsuranceName(Optional<Person> person) { 
 return person.flatMap(Person::getCar) 
 .flatMap(Car::getInsurance) 
 .map(Insurance::getName) 
 .orElse("Unknown"); // 如果Optional的结果值为空,设置默认值
} 
  1. 使用 Optional 解引用串接的 Person/Car/Insurance 对象

《Java8实战》第11章 用 Optional 取代 null

11.3.4 操纵由 Optional 对象构成的 Stream

Java 9 引入了 Optional 的 stream()方法,使用该方法可以把一个含值的 Optional 对象转换成由该值构成的 Stream 对象,或者把一个空的 Optional 对象转换成等价的空 Stream。
找出 person 列表所使用的保险公司名称(不含重复项)
《Java8实战》第11章 用 Optional 取代 null

11.3.5 默认行为及解引用 Optional 对象

orElse方法默认值,当遭遇空的 Optional 变量时,默认值会作为该方法的调用返回值
Optional提供的变量

  • get() ,这个方法不太安全还是会抛出空指针异常的,
  • orElse(T other) 它允许你在Optional 对象不包含值时提供一个默认值。
  • orElseGet(Supplier<? extends="" t=""?> other)是 orElse 方法的延迟调用版,因为 Supplier 方法只有在 Optional 对象不含值时才执行调用
  • or(Supplier<? extends=""?><? extends="" t=""?>> supplier)与前面介绍的orElseGet 方法很像,不过它不会解包 Optional 对象中的值,即便该值是存在的。
  • orElseThrow(Supplier<? extends="" x=""?> exceptionSupplier)和 get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow你可以定制希望抛出的异常类型
  • ifPresent(Consumer<? super="" t=""?>consumer)变量值存在时,执行一个以参数形式传入的方法,否则就不进行任何操作。
11.3.6 两个 Optional 对象的组合

方法,它接受一个 Person 和一个 Car 对象,设计一个普通版本和安全的版本

// 普通版本
public Insurance findCheapestInsurance(Person person, Car car) { 
 // 不同的保险公司提供的查询服务
 // 对比所有数据
 return cheapestCompany; 
} 

// 安全版本
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) { 
 if (person.isPresent() && car.isPresent()) { 
   return Optional.of(findCheapestInsurance(person.get(), car.get())); 
 } else { 
   return Optional.empty(); 
 } 
} 

但是这个安全版本的方法和之前的判null条件太像了

11.3.7 使用 filter 剔除特定的值

你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要检查保险公司的名称是否为“Cambridge-Insurance”。

常规方式
Insurance insurance = ...; 
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){ 
 System.out.println("ok"); 
} 

使用Optional 对象的 filter 方法
Optional<Insurance> optInsurance = ...; 
optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())) 
 .ifPresent(x -> System.out.println("ok")); 

filter 方法接受一个谓词作为参数。如果 Optional 对象的值存在,并且它符合谓词的条件,filter 方法就返回其值;否则它就返回一个空的 Optional 对象。

public String getCarInsuranceName(Optional<Person> person, int minAge) { 
 return person.filter(p -> p.getAge() >= minAge) 
 .flatMap(Person::getCar) 
 .flatMap(Car::getInsurance) 
 .map(Insurance::getName) 
 .orElse("Unknown"); 
} 

《Java8实战》第11章 用 Optional 取代 null

11.4 使用 Optional 的实战示例

有效地使用 Optional 类意味着你需要对如何处理潜在缺失值进行全面的反思。

11.4.1 用 Optional 封装可能为 null 的值

大多数情况下,你可能希望这些方法能返回一个 Optional 对象。你无法修改这些方法的签名,但是你很容易用 Optional 对这些方法的返回值进行封装。
Object value = map.get("key");
如果没有该key,就是返回null。可以使用 Optional 封装 map 的返回值,
Optional<Object> value = **Optional.ofNullable**(map.get("key"));

11.4.2 异常与 Optional 的对比

函数无法返回某个值,这时除了返回 null,Java API 比较常见的替代做法是抛出一个异常。
典型的例子是使用静态方法 Integer.parseInt(String),将String 转换为 int。在这个例子中,如果 String 无法解析到对应的整型,该方法就抛出一个NumberFormatException。
与之前不同的是,这次你需要使用 try/catch 语句,而不是使用 if 条件判断来控制一个变量的值是否非空。
也可以用空的 Optional 对象,对遭遇无法转换的 String 时返回的非法值进行建模,这时你期望 parseInt 的返回值是一个 Optional。
我们修改不了之前的方法,但是我们可以封装一个新的工具方法

public static Optional<Integer> stringToInt(String s) { 
 try { 
	// 如果 String 能转换为对应的 Integer,将其封装在 Optional 对象中返回
   return Optional.of(Integer.parseInt(s)); 
 } catch (NumberFormatException e) { 
	//否则返回一个空的 Optional对象
   return Optional.empty(); 
 } 
}

我们的建议是,你可以将多个类似的方法封装到一个工具类中,让我们称之为OptionalUtility。通过这种方式,你以后就能直接调用 OptionalUtility.stringToInt方法,将 String 转换为一个 Optional对象,而不再需要记得你在其中封装了笨拙的 try/catch 的逻辑了

11.4.3 基础类型的 Optional 对象,以及为什么应该避免使用它们

与 Stream 对象一样,Optional 也提供了类似的基础类型—— OptionalInt、OptionalLong 以及 OptionalDouble
有些地方可以不返回 Optional,而是直接返回一个 OptionalInt 类型的对象

11.4.4 把所有内容整合起来
Properties props = new Properties(); 
props.setProperty("a", "5"); 
props.setProperty("b", "true"); 
props.setProperty("c", "-3"); 

假设你的程序需要从这些属性中读取一个值,该值是以秒为单位计量的一段时间。由于一段时间必须是正数,你想要该方法符合下面的签名:
public int readDuration(Properties props, String name)
如果给定属性对应的值是一个代表正整数的字符串,就返回该整数值,任何其他的情况都返回 0。采用 JUnit 的断言

assertEquals(5, readDuration(param, "a")); 
assertEquals(0, readDuration(param, "b")); 
assertEquals(0, readDuration(param, "c")); 
assertEquals(0, readDuration(param, "d")); 

readDuration的实现

传统的命令方式
public int readDuration(Properties props, String name) { 
 String value = props.getProperty(name); 
 if (value != null) { // 确保名称对应的属性存在
   try { 
// 将 String 属性转换为数字类型
     int i = Integer.parseInt(value); 
     if (i > 0) { 
       return i; 
     } 
   } catch (NumberFormatException nfe) { } 
 } 
 return 0; 
}

如果需要访问的属性值不存在,Properties.getProperty(String)方法的返回值就是一个 null,使用 ofNullable 工厂方法可以方便地将该值转换为 Optional 对象。接着,你可以向它的 flatMap 方法传递代码清单 11-7 中实现的 OptionalUtility. stringToInt 方法的引用,将 Optional转换为 Optional。最后,你非常轻易地就可以过滤掉负数。这种方式下,如果任何一个操作返回一个空的 Optional 对象,该方法都会返回 rElse 方法设置的默认值 0;否则就返回封装在 Optional 对象中的正整数。文章来源地址https://www.toymoban.com/news/detail-418785.html

public int readDuration(Properties props, String name) { 
 return Optional.ofNullable(props.getProperty(name)) 
 .flatMap(OptionalUtility::stringToInt) 
 .filter(i -> i > 0) 
 .orElse(0); 
}

11.5 小结

  • null 引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
  • Java 8 中引入了一个新的类 java.util.Optional,对存在或缺失的变量值进行建模。
  • 你可以使用静态工厂方法 Optional.empty、Optional.of 以及 Optional.ofNullable创建 Optional 对象。
  • Optional 类支持多种方法,比如 map、flatMap、filter,它们在概念上与 Stream类中对应的方法十分相似。
  • 使用 Optional 会迫使你更积极地解引用 Optional 对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
  • 使用 Optional 能帮助你设计更好的 API,用户只需要阅读方法签名,就能了解该方法是否接受一个 Optional 类型的值

到了这里,关于《Java8实战》第11章 用 Optional 取代 null的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java8里如何使用流?《Java8 实战》读书笔记 第 5 章 使用流

    [实践总结] java8 展平嵌套数组 [实践总结] java8 列表根据元素属性去重 [实践总结] String,int[],Integer[],List<Integer>互转 [实践总结] String,String[],List<String>互转 [实践总结] String,int,Integer互转 ✅ Further Reading : java8 数值流介绍 ✅ Further Reading : java8 构建流的5种方法

    2024年02月16日
    浏览(35)
  • Windows11配置Java8开发环境 - JDK1.8

    1、下载JDK 我们要下载的是Oracle版本的JDK,我们首先进入Oracle的官网的Java下载页面:https://www.oracle.com/cn/java/technologies/downloads/ 一直往下滑 ,找到 Java8 —点击 Windows (如果你是其他系统选择你对应的系统即可)— 在下方根据你的电脑系统类型选择对应的X86或X64然后点击下载

    2024年02月10日
    浏览(45)
  • 《Java8实战》第9章 重构、测试和调试

    9.1 为改善可读性和灵活性重构代码 Lambda 表达式可以帮助我们用更紧凑的方式描述程序的行为。 9.1.1 改善代码的可读性 可读性非常主观,但是通俗的理解就是“别人理解这段代码的难易程度”。 改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有人理解

    2023年04月23日
    浏览(66)
  • 《Java8实战》第8章 Collection API 的增强功能

    8.1 集合工厂 如果我想创建一个集合,之前的做法是先new一个list,然后再一个个的add,这样子有点繁琐。 现在的方法可以这样,是使用 Arrays.asList()工厂方法: ListString friends = Arrays.asList(\\\"Raphael\\\", \\\"Olivia\\\", \\\"Thibaut\\\"); 但是这样子创建只能更新,不可以增加、删除。 8.1.1 List 工厂 工

    2023年04月24日
    浏览(33)
  • 设计模式、Java8新特性实战 - List<T> 抽象统计组件

    在日常写代码的过程中,针对List集合,统计里面的某个属性,是经常的事情,针对List的某个属性的统计,我们目前大部分时候的代码都是这样写,每统计一个变量,就要定义一个值,且要在for循环增加一行累计的代码,比较繁琐,而且代码写出来不够优雅。 利用顶层抽象的

    2024年02月14日
    浏览(33)
  • 11.初始JavaScript[初步了解何为js]

    大家好,我是晓星航。今天为大家带来的是 JavaScript的简单介绍 相关的讲解!😀 JavaScript (简称 JS) 是世界上最流行的编程语言之一 是一个脚本语言, 通过解释器运行 主要在客户端(浏览器)上运行, 现在也可以基于 node.js 在服务器端运行. JavaScript 最初只是为了完成简单的表单验

    2024年02月08日
    浏览(48)
  • Windows 11 中如何为 Linux 安装 Windows 子系统:详细教程

    微软首先通过 2016 年在 Windows 10 周年更新中引入的 Windows Subsystem for Linux (WSL) 提供了一定程度的 Linux 兼容性,该实用程序的初始版本并不完美。 WSL 2 在 2019 年 6 月改变了这一点,通过操作系统的内置 Hyper-V 管理程序将完整的 Linux 内核直接整合到 Windows 10 中。 安装 WSL 曾经是

    2024年02月10日
    浏览(50)
  • Optional 类 - Java 8

    2023年04月18日
    浏览(34)
  • Java Optional

    Optional 是 Java 8 中引入的一个类,用于处理可能为空的值。它提供了一种优雅的方式来避免空指针异常,并在值存在时进行处理。下面是一些 Optional 的常用方法: 创建 Optional 对象: Optional.empty() : 创建一个空的 Optional 对象。 Optional.of(value) : 创建一个包含指定非空值的 Optio

    2024年02月07日
    浏览(36)
  • Java新特性:Optional类

    Java新特性:Optional类 Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。 Java 8 引入 Optional 类,用来解决 NullPointerException。 Optional 代

    2024年02月16日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包