《Java8 实战》- 第十章笔记(用 Optional 取代 null)

本贴最后更新于 2219 天前,其中的信息可能已经东海扬尘

用 Optional 取代 null

如果你作为 Java 程序员曾经遭遇过 NullPointerException,请举起手。如果这是你最常遭遇的异常,请继续举手。非常可惜,这个时刻,我们无法看到对方,但是我相信很多人的手这个时刻是举着的。我们还猜想你可能也有这样的想法:“毫无疑问,我承认,对任何一位 Java 程序员来说,无论是初出茅庐的新人,还是久经江湖的专家,NullPointerException 都是他心中的痛,可是我们又无能为力,因为这就是我们为了使用方便甚至不可避免的像 null 引用这样的构造所付出的代价。”这就是程序设计世界里大家都持有的观点,然而,这可能并非事实的全部真相,只是我们根深蒂固的一种偏见。

1965 年,英国一位名为 Tony Hoare 的计算机科学家在设计 ALGOL W 语言时提出了 null 引用的想法。ALGOL W 是第一批在堆上分配记录的类型语言之一。Hoare 选择 null 引用这种方式,“只是因为这种方法实现起来非常容易”。虽然他的设计初衷就是要“通过编译器的自动检测机制,确保所有使用引用的地方都是绝对安全的”,他还是决定为 null 引用开个绿灯,因为他认为这是为“不存在的值”建模最容易的方式。很多年后,他开始为自己曾经做过这样的决定而后悔不迭,把它称为“我价值百万的重大失误”。我们已经看到它带来的后果——程序员对对象的字段进行检查,判断它的值是否为期望的格式,最终却发现我们查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的 NullPointerException 异常。

实际上,Hoare 的这段话低估了过去五十年来数百万程序员为修复空引用所耗费的代价。近十年出现的大多数现代程序设计语言,包括 Java,都采用了同样的设计方式,其原因是为了与更老的语言保持兼容,或者就像 Hoare 曾经陈述的那样,“仅仅是因为这样实现起来更加容易”。让我们从一个简单的例子入手,看看使用 null 都有什么样的问题。

如何为缺失的值建模

假设你需要处理下面这样的嵌套对象,这是一个拥有汽车及汽车保险的客户。

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,结果又会怎样?

采用防御式检查减少 NullPointerException

怎样做才能避免这种不期而至的 NullPointerException 呢?通常,你可以在需要的地方添加 null 的检查(过于激进的防御式检查甚至会在不太需要的地方添加检测代码),并且添加的方式往往各有不同。下面这个例子是我们试图在方法中避免 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";
}

这个方法每次引用一个变量都会做一次 null 检查,如果引用链上的任何一个遍历的解变量值为 null,它就返回一个值为“Unknown”的字符串。唯一的例外是保险公司的名字,你不需要对它进行检查,原因很简单,因为任何一家公司必定有个名字。注意到了吗,由于你掌握业务领域的知识,避免了最后这个检查,但这并不会直接反映在你建模数据的 Java 类之中。

我们将上面的代码标记为“深层质疑”,原因是它不断重复着一种模式:每次你不确定一个变量是否为 null 时,都需要添加一个进一步嵌套的 if 块,也增加了代码缩进的层数。很明显,这种方式不具备扩展性,同时还牺牲了代码的可读性。面对这种窘境,你也许愿意尝试另一种方案。下面的代码清单中,我们试图通过一种不同的方式避免这种问题。

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();
}

第二种尝试中,你试图避免深层递归的 if 语句块,采用了一种不同的策略:每次你遭遇 null 变量,都返回一个字符串常量“Unknown”。然而,这种方案远非理想,现在这个方法有了四个截然不同的退出点,使得代码的维护异常艰难。更糟的是,发生 null 时返回的默认值,即字符串“Unknown”在三个不同的地方重复出现——出现拼写错误的概率不小!当然,你可能会说,我们可以用把它们抽取到一个常量中的方式避免这种问题。

进一步而言,这种流程是极易出错的;如果你忘记检查了那个可能为 null 的属性会怎样?通过这一章的学习,你会了解使用 null 来表示变量值的缺失是大错特错的。你需要更优雅的方式来对缺失的变量值建模。

null 带来的种种问题

让我们一起回顾一下到目前为止进行的讨论,在 Java 程序开发中使用 null 会带来理论和实际操作上的种种问题。

  1. 它是错误之源。NullPointerException 是目前 Java 程序开发中最典型的异常。
  2. 它会使你的代码膨胀。它让你的代码充斥着深度嵌套的 null 检查,代码的可读性糟糕透顶。
  3. 它自身是毫无意义的。null 自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模。
  4. 它破坏了 Java 的哲学。Java 一直试图避免让程序员意识到指针的存在,唯一的例外是:null 指针。
  5. 它在 Java 的类型系统上开了个口子。null 并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题,原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个 null 变量最初的赋值到底是什么类型。

Optional 类入门

为了更好的解决和避免 NPE 异常,Java 8 中引入了一个新的类 java.util.Optional。这是一个封装 Optional 值的类。举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么 Person 类内部的 car 变量就不应该声明为 Car,遭遇某人没有车时把 null 引用赋值给它,而是将其声明为 Optional类型。

变量存在时,Optional 类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的 Optional 对象,由方法 Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回 Optional 类的特定单一实例。你可能还有疑惑,null 引用和 Optional.empty()有什么本质的区别吗?从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大: 如果你尝试解引用一个 null , 一定会触发 NullPointerException , 不过使用 Optional.empty()就完全没事儿,它是 Optional 类的一个有效对象,多种场景都能调用,非常有用。关于这一点,接下来的部分会详细介绍。

使用 Optional 而不是 null 的一个非常重要而又实际的语义区别是,第一个例子中,我们在声明变量时使用的是 Optional类型,而不是 Car 类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用 Car 这样的类型,可能将变量赋值为 null,这意味着你需要独立面对这些,你只能依赖你对业务模型的理解,判断一个 null 是否属于该变量的有效范畴。

牢记上面这些原则,你现在可以使用 Optional 类对最初的代码进行重构,结果如下。

public class Person {
    private Optional<Car> car;

    public Optional<Car> getCar() {
        return car;
    }
}
public class Insurance {
    private String name;

    public String getName() {
        return name;
    }
}
public class Car {
    private Optional<Insurance> insurance;

    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}

发现 Optional 是如何丰富你模型的语义了吧。代码中 person 引用的是 Optional,而 car 引用的是 Optional,这种方式非常清晰地表达了你的模型中一个 person 可能拥有也可能没有 car 的情形,同样,car 可能进行了保险,也可能没有保险。

与此同时,我们看到 insurance 公司的名称被声明成 String 类型,而不是 Optional,这非常清楚地表明声明为 insurance 公司的类型必须提供公司名称。使用这种方式,一旦解引用 insurance 公司名称时发生 NullPointerException,你就能非常确定地知道出错的原因,不再需要为其添加 null 的检查,因为 null 的检查只会掩盖问题,并未真正地修复问题。insurance 公司必须有个名字,所以,如果你遇到一个公司没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

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

应用 Optional 的几种模式

到目前为止,一切都很顺利;你已经知道了如何使用 Optional 类型来声明你的域模型,也了解了这种方式与直接使用 null 引用表示变量值的缺失的优劣。但是,我们该如何使用呢?用这种方式能做什么,或者怎样使用 Optional 封装的值呢?

创建 Optional 对象

使用 Optional 之前,你首先需要学习的是如何创建 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.ofNullable,你可以创建一个允许 null 值的 Optional 对象:

Optional<Car> optCar = Optional.ofNullable(car);

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

你可能已经猜到,我们还需要继续研究“如何获取 Optional 变量中的值”。尤其是,Optional 提供了一个 get 方法,它能非常精准地完成这项工作,我们在后面会详细介绍这部分内容。不过 get 方法在遭遇到空的 Optional 对象时也会抛出异常,所以不按照约定的方式使用它,又会让我们再度陷入由 null 引起的代码维护的梦魇。因此,我们首先从无需显式检查的 Optional 值的使用入手,这些方法与 Stream 中的某些操作极其相似。

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

从对象中提取信息是一种比较常见的模式。比如,你可能想要从 insurance 公司对象中提取公司的名称。提取名称之前,你需要检查 insurance 对象是否为 null,代码如下所示:

String name = null;
if(insurance != null){
    name = insurance.getName();
}

为了支持这种模式,Optional 提供了一个 map 方法。它的工作方式如下:

Optional<Insurance> optionalInsurance = Optional.ofNullable(insurance);
Optional<String> name = optionalInsurance.map(Insurance::getName);

从概念上,这与我们在第 4 章和第 5 章中看到的流的 map 方法相差无几。map 操作会将提供的函数应用于流的每个元素。你可以把 Optional 对象看成一种特殊的集合数据,它至多包含一个元素。如果 Optional 包含一个值,那函数就将该值作为参数传递给 map,对该值进行转换。如果 Optional 为空,就什么也不做。

这看起来挺有用,但是你怎样才能应用起来,重构之前的代码呢?前文的代码里用安全的方式链接了多个方法。

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

为了达到这个目的,我们需要求助 Optional 提供的另一个方法 flatMap。

使用 flatMap 链接 Optional 对象

由于我们刚刚学习了如何使用 map,你的第一反应可能是我们可以利用 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类型的对象,这意味着 map 操作的结果是一个 Optional<Optional> 类型的对象。因此,它对 getInsurance 的调用是非法的,因为最外层的 optional 对象包含了另一个 optional 对象的值,而它当然不会支持 getInsurance 方法。

所以,我们该如何解决这个问题呢?让我们再回顾一下你刚刚在流上使用过的模式:flatMap 方法。使用流时,flatMap 方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,最终形成一个新的流的流。但是 flagMap 会用流的内容替换每个新生成的流。换句话说,由方法生成的各个流会被合并或者扁平化为一个单一的流。这里你希望的结果其实也是类似的,但是你想要的是将两层的 optional 合并为一个。

Stream 和 Optional 的 flagMap 方法对比

这个例子中,传递给流的 flatMap 方法会将每个正方形转换为另一个流中的两个三角形。那么,map 操作的结果就包含有三个新的流,每一个流包含两个三角形,但 flatMap 方法会将这种两层的流合并为一个包含六个三角形的单一流。类似地,传递给 optional 的 flatMap 方法的函数会将原始包含正方形的 optional 对象转换为包含三角形的 optional 对象。如果将该方法传递给 map 方法,结果会是一个 Optional 对象,而这个 Optional 对象中包含了三角形;但 flatMap 方法会将这种两层的 Optional 对象转换为包含三角形的单一 Optional 对象。

  1. 使用 Optional 获取 car 的保险公司名称

相信现在你已经对 Optional 的 map 和 flatMap 方法有了一定的了解,让我们看看如何应用。

public String getCarInsuranceName(Optional<Person> person) {
        return person.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                // 如果Optional的j结果为空值,设置默认值
                .orElse("Unknown");
}

我们可以看到,处理潜在可能缺失的值时,使用 Optional 具有明显的优势。这一次,你可以用非常容易却又普适的方法实现之前你期望的效果——不再需要使用那么多的条件分支,也不会增加代码的复杂性。

  1. 使用 Optional 解引用串接的 Person/Car/Insurance 对象

由 Optional对象,我们可以结合使用之前介绍的 map 和 flatMap 方法,从 Person 中解引用出 Car,从 Car 中解引用出 Insurance,从 Insurance 对象中解引用出包含 insurance 公司名称的字符串。

使用 Optional 解引用串接的 Person/Car/Insurance

这里,我们从以 Optional 封装的 Person 入手,对其调用 flatMap(Person::getCar)。如前所述,这种调用逻辑上可以划分为两步。第一步,某个 Function 作为参数,被传递给由 Optional 封装的 Person 对象,对其进行转换。这个场景中,Function 的具体表现是一个方法引用,即对 Person 对象的 getCar 方法进行调用。由于该方法返回一个 Optional类型的对象,Optional 内的 Person 也被转换成了这种对象的实例,结果就是一个两层的 Optional 对象,最终它们会被 flagMap 操作合并。从纯理论的角度而言,你可以将这种合并操作简单地看成把两个 Optional 对象结合在一起,如果其中有一个对象为空,就构成一个空的 Optional 对象。如果你对一个空的 Optional 对象调用 flatMap,实际情况又会如何呢?结果不会发生任何改变,返回值也是个空的 Optional 对象。与此相反,如果 Optional 封装了一个 Person 对象,传递给 flapMap 的 Function,就会应用到 Person 上对其进行处理。这个例子中,由于 Function 的返回值已经是一个 Optional 对象,flapMap 方法就直接将其返回。

第二步与第一步大同小异,它会将 Optional转换为 Optional。第三步则会将 Optional转化为 Optional对象,由于 Insurance.getName()方法的返回类型为 String,这里就不再需要进行 flapMap 操作了。

截至目前为止,返回的 Optional 可能是两种情况:如果调用链上的任何一个方法返回一个空的 Optional,那么结果就为空,否则返回的值就是你期望的保险公司的名称。那么,你如何读出这个值呢?毕竟你最后得到的这个对象还是个 Optional,它可能包含保险公司的名称,也可能为空。我们使用了一个名为 orElse 的方法,当 Optional 的值为空时,它会为其设定一个默认值。除此之外,还有很多其他的方法可以为 Optional 设定默认值,或者解析出 Optional 代表的值。接下来我们会对此做进一步的探讨。

默认行为及解引用 Optional 对象

我们决定采用 orElse 方法读取这个变量的值,使用这种方式你还可以定义一个默认值,遭遇空的 Optional 变量时,默认值会作为该方法的调用返回值。Optional 类提供了多种方法读取 Optional 实例中的变量值。

  1. get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个 NoSuchElementException 异常。所以,除非你非常确定 Optional 变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于嵌套式的 null 检查,也并未体现出多大的改进。
  2. orElse(T other)是我们在代码使用的方法,正如之前提到的,它允许你在 Optional 对象不包含值时提供一个默认值。
  3. orElseGet(Supplier<? extends T> other)是 orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行调用。如果创建默认值是件耗时费力的工作,你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在 Optional 为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
  4. orElseThrow(Supplier<? extends X> exceptionSupplier)和 get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 你可以定制希望抛出的异常类型。
  5. ifPresent(Consumer<? super T>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

Optional 类和 Stream 接口的相似之处,远不止 map 和 flatMap 这两个方法。还有第三个方法 filter,它的行为在两种类型之间也极其相似。

两个 Optional 对象的组合

现在,我们假设你有这样一个方法,它接受一个 Person 和一个 Car 对象,并以此为条件对外部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:

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

我们还假设你想要该方法的一个 null-安全的版本,它接受两个 Optional 对象作为参数,返回值是一个 Optional对象,如果传入的任何一个参数值为空,它的返回值亦为空。Optional 类还提供了一个 isPresent 方法,如果 Optional 对象包含值,该方法就返回 true,所以你的第一想法可能是通过下面这种方式实现该方法:

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();
    }
}

这个方法具有明显的优势,我们从它的签名就能非常清楚地知道无论是 person 还是 car,它的值都有可能为空,出现这种情况时,方法的返回值也不会包含任何值。不幸的是,该方法的具体实现和你之前曾经实现的 null 检查太相似了:方法接受一个 Person 和一个 Car 对象作为参数,而二者都有可能为 null。利用 Optional 类提供的特性,有没有更好或更地道的方式来实现这个方法呢?

Optional 类和 Stream 接口的相似之处远不止 map 和 flatMap 这两个方法。还有第三个方法 filter,它的行为在两种类型之间也极其相似,我们在接下来的一节会进行介绍。

使用 filter 剔除特定的值

你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要检查保险公司的名称是否为“Cambridge-Insurance”。为了以一种安全的方式进行这些操作,你首先需要确定引用指向的 Insurance 对象是否为 null,之后再调用它的 getName 方法,如下所示:

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 对象。如果你还记得我们可以将 Optional 看成最多包含一个元素的 Stream 对象,这个方法的行为就非常清晰了。如果 Optional 对象为空,它不做任何操作,反之,它就对 Optional 对象中包含的值施加谓词操作。如果该操作的结果为 true,它不做任何改变,直接返回该 Optional 对象,否则就将该值过滤掉,将 Optional 的值置空。

下一节中,我们会探讨 Optional 类剩下的一些特性,并提供更实际的例子,展示多种你能够应用于代码中更好地管理缺失值的技巧。

使用 Optional 的实战示例

相信你已经了解,有效地使用 Optional 类意味着你需要对如何处理潜在缺失值进行全面的反思。这种反思不仅仅限于你曾经写过的代码,更重要的可能是,你如何与原生 Java API 实现共存共赢。

实际上,我们相信如果 Optional 类能够在这些 API 创建之初就存在的话,很多 API 的设计编写可能会大有不同。为了保持后向兼容性,我们很难对老的 Java API 进行改动,让它们也使用 Optional,但这并不表示我们什么也做不了。你可以在自己的代码中添加一些工具方法,修复或者绕过这些问题,让你的代码能享受 Optional 带来的威力。我们会通过几个实际的例子讲解如何达到这样的目的。

用 Optional 封装可能为 null 的值

现存 Java API 几乎都是通过返回一个 null 的方式来表示需要值的缺失,或者由于某些原因计算无法得到该值。比如,如果 Map 中不含指定的键对应的值,它的 get 方法会返回一个 null。但是,正如我们之前介绍的,大多数情况下,你可能希望这些方法能返回一个 Optional 对象。你无法修改这些方法的签名,但是你很容易用 Optional 对这些方法的返回值进行封装。我们接着用 Map 做例子,假设你有一个 Map<String, Object> 方法,访问由 key 索引的值时,如果 map 中没有与 key 关联的值,该次调用就会返回一个 null。

Object value = map.get("key");

使用 Optional 封装 map 的返回值,你可以对这段代码进行优化。要达到这个目的有两种方式:你可以使用笨拙的 if-then-else 判断语句,毫无疑问这种方式会增加代码的复杂度;或者你可以采用我们前文介绍的 Optional.ofNullable 方法:

Optional<Object> value = Optional.ofNullable(map.get("key"));

每次你希望安全地对潜在为 null 的对象进行转换,将其替换为 Optional 对象时,都可以考虑使用这种方法。

异常与 Optional 的对比

由于某种原因,函数无法返回某个值,这时除了返回 null,Java API 比较常见的替代做法是抛出一个异常。这种情况比较典型的例子是使用静态方法 Integer.parseInt(String),将 String 转换为 int。在这个例子中,如果 String 无法解析到对应的整型,该方法就抛出一个 NumberFormatException。最后的效果是,发生 String 无法转换为 int 时,代码发出一个遭遇非法参数的信号,唯一的不同是,这次你需要使用 try/catch 语句,而不是使用 if 条件判断来控制一个变量的值是否非空。

你也可以用空的 Optional 对象,对遭遇无法转换的 String 时返回的非法值进行建模,这时你期望 parseInt 的返回值是一个 optional。我们无法修改最初的 Java 方法,但是这无碍我们进行需要的改进,你可以实现一个工具方法,将这部分逻辑封装于其中,最终返回一个我们希望的 Optional 对象,代码如下所示。

public static Optional<Integer> stringToInt(String s) {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

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

代码

Github: chap10

Gitee: chap10

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...