Java 8 - Optional全解(上)

简介: Java 8 - Optional全解(上)

20200510181139786.png


在Optional出现之前经常遇到的空指针异常


NullPointerException 这个你总不能说你没有碰到过吧


【Person / Car / Insurance 的数据模型】

public class Person {
  private Car car;
  public Car getCar() { return car; }
}
public class Car {
  private Insurance insurance;
  public Insurance getInsurance() { return insurance; }
}
public class Insurance {
  private String name;
  public String getName() { return name; }
}


那么,下面这段代码存在怎样的问题呢?

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


段代码看起来相当正常,但是现实生活中很多人没有车。所以调用 getCar 方法的结果会怎样呢?在实践中,一种比较常见的做法是返回一个 null 引用,表示该值的缺失,即用户没有车。


而接下来,对 getInsurance 的调用会返回 null 引用的 insurance ,这会导致运行时出现一个 NullPointerException ,终止程序的运行。但这还不是全部。如果返回的 person 值为 null会怎样?如果 getInsurance 的返回值也是 null ,结果又会怎样?

20210314102649935.png


采用防御式减少NullPointerException (深度质疑)


怎样做才能避免这种不期而至的 NullPointerException 呢?通常,你可以在需要的地方添加 null 的检查(过于激进的防御式检查甚至会在不太需要的地方添加检测代码),并且添加的方式往往各有不同。


下面这个例子是我们试图在方法中避免 NullPointerException 的第一次尝试

2021031410334378.png


这个方法每次引用一个变量都会做一次 null 检查,如果引用链上的任何一个遍历的解变量

值为 null ,它就返回一个值为“Unknown”的字符串。


每次你不确定一个变量是否为 null 时,都需要添加一个进一步嵌套的 if 块,也增加了代码缩进的层数。很明显,这种方式不具备扩展性,同时还降低了代码的可读性。


面对这种情况,你也许愿意尝试另一种方案。下面的代码清单中,我们试图通过一种不同的方式避免这种问题。


null-安全的第二种尝试(过多的退出语句)


2021031410504525.png


为了避免深层递归的 if 语句块,采用了一种不同的策略: 每次遇到null, 都返回一个unknown常量。


然而,这种方案远非理想,现在这个方法有了四个不同的退出点,使得代码的维护异常困难。


更糟糕的是,发生 null 时返回的默认值,即字符串“Unknown”在三个不同的地方重复出现——出现拼写写错误的概率不小!当然,你可能会说,我们可以用把它们抽取到一个常量中的方式避免这种问题。


进一步而言,这种流程是极易出错的;如果你忘记检查了那个可能为 null 的属性会怎样?


使用 null 来表示变量值的缺失是大错特错的。你需要更优雅的方式来对缺失的变量值建模。


Optional的介绍以及API的详解


Java 8中引入了一个新的类 java.util.Optional<T> 。这是一个封装 Optional 值的类。

举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么 Person 类内部的 car 变量就不应该声明为 Car ,遭遇某人没有车时把 null 引用赋值给它,而是应该像下图那样直接将其声明为 Optional<Car> 类型。


20210314110950909.png


变量存在时, Optional 类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的 Optional 对象,由方法 Optional.empty() 返回

Optional.empty() 方法是一个静态工厂,方法,它返回 Optional 类的特定单一实例。

20210314111107868.png


null VS Optional.empty


引 用 一 个 null , 一 定 会 触 发 NullPointerException , 不 过 使 用Optional.empty() 就完全没事儿,它是 Optional 类的一个有效对象,多种场景都能调用,非常有用.


使用 Optional 而不是 null 的一个非常重要而又实际的语义区别是,第一个例子中,我们在声明变量时使用的是 Optional<Car> 类型,而不是 Car 类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。

public class Person {
    private Optional<Car> car;
    public Optional<Car> getCar() {
        return this.car;
    }
}
public class Car {
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}

与此相反,使用 Car 这样的类型,可能将变量赋值为 null ,这意味着你需要独立面对这些,你只能依赖你对业务模型的理解,判断一个 null 是否属于该变量的有效范围。


使用Optional 重新定义数据模型


20210314111526596.png



代码中 person 引用的是 Optional<Car>而 car 引用的是 Optional<Insurance> ,这种方式非常清晰地表达了你的模型中一个 person可能有也可能没有 car 的情形,同样, car 可能进行了保险,也可能没有保险。


与此同时,我们看到 insurance 的名称被声明成 String 类型,而不是 Optional<String> ,这非常清楚地表明声明为 insurance 的类型必须提供名称。


使用这种方式,一旦解引用 insurance 名称时发生 NullPointerException ,你就能非常确定地知道出错的原因,不再需要为其添加 null 的检查,因为 null 的检查只会掩盖问题,并未真正地修复问题。


insurance 必须有个名字,所以,如果你遇到一个没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。


在代码中始终如一地使用 Optional ,能非常清晰地界定出变量值的缺失是结构上的问题,还是你算法上的缺陷,或是你数据中的问题。


另外, 引入 Optional类的意图并非要消除每一个 null 引用。与此相反,它的目标是帮助你更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个 Optional 的值。这种强制会让你更积极地将变量从 Optional 中解包出来,直面缺失的变量值。


如何使用 Optional


创建Optional对象


使用 Optional 之前,你首先需要学习的是如何创建 Optional 对象。完成这一任务有多种方法。


1.声明一个空的Optional


可以通过静态工厂方法 Optional.empty ,创建一个空的 Optional对象

Optional<Car> car = Optional.empty();


2.使用一个非空创建Optional

还可以使用静态工厂方法 Optional.of ,依据一个非空值创建一个 Optional 对象:

  Car car1 = new Car();
  Optional<Car> o = Optional.of(car1);


如果 car 是一个 null ,这段代码会立即?出一个 NullPointerException ,而不是等到你试图访问 car 的属性值时才返回一个错误。

Optional<Car> o = Optional.of(null);

20210314113547731.png


3.可接受null值的Optional

最后,使用静态工厂方法 Optional.ofNullable ,你可以创建一个允许 null 值的 Optional对象

Car car1 = new Car();
Optional<Car> o = Optional.ofNullable(car1);
System.out.println(o);
Optional<Car> o2 = Optional.ofNullable(null);
System.out.println(o2);


如果 car 是 null ,那么得到的 Optional 对象就是个空对象。

20210314113717808.png


创建完成了, 我们还需要继续研究“如何获取 Optional 变量中的值”。尤其是, Optional提供了一个 get 方法,它能非常精准地完成这项工作,我们在后面会详细介绍这部分内容。


不过get 方法在遇到空的 Optional 对象时也会抛出异常,所以不按照约定的方式使用它,又会让我们再度陷入由 null 引起的代码维护的梦魇。因此,我们首先从无需显式检查的 Optional 值的使用入手,这些方法与 Stream 中的某些操作极其相似。

相关文章
|
18天前
|
自然语言处理 Java API
Java 8的Stream API和Optional类:概念与实战应用
【5月更文挑战第17天】Java 8引入了许多重要的新特性,其中Stream API和Optional类是最引人注目的两个。这些特性不仅简化了集合操作,还提供了更好的方式来处理可能为空的情况,从而提高了代码的健壮性和可读性。
45 7
|
20天前
|
安全 Java 开发者
Java一分钟之-Optional类:优雅处理null值
【5月更文挑战第13天】Java 8的`Optional`类旨在减少`NullPointerException`,提供优雅的空值处理。本文介绍`Optional`的基本用法、创建、常见操作,以及如何避免错误,如直接调用`get()`、误用`if (optional != null)`检查和过度使用`Optional`。正确使用`Optional`能提高代码可读性和健壮性,建议结合实际场景灵活应用。
35 3
|
20天前
|
Java 编译器 API
Java基础教程(17)-Java8中的lambda表达式和Stream、Optional
【4月更文挑战第17天】Lambda表达式是Java 8引入的函数式编程特性,允许函数作为参数或返回值。它有简洁的语法:`(parameters) -> expression 或 (parameters) ->{ statements; }`。FunctionalInterface注解用于标记单方法接口,可以用Lambda替换。
|
20天前
|
存储 Java API
java8新特性 lambda表达式、Stream、Optional
java8新特性 lambda表达式、Stream、Optional
|
20天前
|
Java
Java8 Optional
Java8 Optional
14 0
|
20天前
|
安全 Java 开发者
Java 8 `Optional` 类的用法和优势
【2月更文挑战第15天】
17 0
|
20天前
|
存储 Java
java8新特性-Optional
java8新特性-Optional
21 0
|
20天前
|
JSON 安全 Java
JAVA8实战 - Optional工具类
JAVA8实战 - Optional工具类
86 0
|
20天前
|
存储 算法 安全
面霸篇:Java 核心集合容器全解(核心卷二)
面霸篇:Java 核心集合容器全解(核心卷二)
39 0
|
20天前
|
安全 Java 程序员
Dating Java8系列之巧用Optional之优雅规避NPE问题
Dating Java8系列之巧用Optional之优雅规避NPE问题
28 0