Java 8 学习笔记10——用Optional取代null

null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。

Java 8中引入了一个新的类java.util.Optional<T>,对存在或缺失的变量值进行建模。

null带来的种种问题

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

Optional类入门

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

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呢?

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

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

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

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

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

代码中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对象。完成这一任务有多种方法。

可以使用静态工厂方法Optional.emptyOptional.of以及Optional.ofNullable创建Optional对象。

1.声明一个空的Optional

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

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

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

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

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

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

3.可接受nullOptional

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

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

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

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

Optional类支持多种方法,比如mapflatMapfilter,它们在概念上与Stream类中对应的方法十分相似。

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

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

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

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

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

从概念上,这与我们之前看到的流的map方法相差无几。map操作会将提供的函数应用于流的每个元素。可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。下图对这种相似性进行了说明,展示了把一个将正方形转换为三角形的函数,分别传递给正方形和Optional正方形流的map方法之后的结果。

在这里插入图片描述

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

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

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

使用flatMap链接Optional对象

你可能会想利用map重写之前的代码,如下所示:

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

但这段代码无法通过编译。optPersonOptional<Person>类型的变量,调用map方法应该没有问题。但getCar返回的是一个Optional<Car>类型的对象,这意味着map操作的结果是一个Optional<Optional<Car>>类型的对象。因此,它对getInsurance的调用是非法的,因为最外层的optional对象包含了另一个optional对象的值,而它当然不会支持getInsurance方法。下图说明了你会遭遇的嵌套式optional结构。

在这里插入图片描述

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

下图说明flatMap方法在StreamOptional类之间的相似性。

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

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

下面看看如何应用OptionalmapflatMap方法。前面的示例用基于Optional的数据模式重写之后,如下所示。

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

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

从具体的代码实现来看,首先修改了getCarInsuranceName方法的签名,因为我们很明确地知道存在这样的用例,即一个不存在的Person被传递给了方法,比如,Person是使用某个标识符从数据库中查询出来的,你想要对数据库中不存在指定标识符对应的用户数据的情况进行建模。你可以将方法的参数类型由Person改为Optional<Person>,对这种特殊情况进行建模。

它通过类型系统让你的域模型中隐藏的知识显式地体现在你的代码中,换句话说,你永远都不应该忘记语言的首要功能就是沟通,即使对程序设计语言而言也没有什么不同。声明方法接受一个Optional参数,或者将结果作为Optional类型返回,让你的同事或者未来你方法的使用者,很清楚地知道它可以接受空值,或者它可能返回一个空值。

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

Optional<Person>对象,可以结合使用之前介绍的mapflatMap方法,从Person中解引用出Car,从Car中解引用出Insurance,从Insurance对象中解引用出包含insurance公司名称的字符串。下图对这种流水线式的操作进行了说明。
在这里插入图片描述
这里,我们从以Optional封装的Person入手,对其调用flatMap(Person:: getCar)。如前所述,这种调用逻辑上可以划分为两步。第一步,某个Function作为参数,被传递给由Optional封装的Person对象,对其进行转换。这个场景中,Function的具体表现是一个方法引用,即对Person对象的getCar方法进行调用。由于该方法返回一个Optional<Car>类型的对象,Optional内的Person也被转换成了这种对象的实例,结果就是一个两层的Optional对象,最终它们会被flagMap操作合并。从纯理论的角度而言,可以将这种合并操作简单地看成把两个Optional对象结合在一起,如果其中有一个对象为空,就构成一个空的Optional对象。

如果对一个空的Optional对象调用flatMap,实际情况又会如何呢?结果不会发生任何改变,返回值也是个空的Optional对象。与此相反,如果Optional封装了一个Person对象,传递给flapMapFunction,就会应用到Person上对其进行处理。这个例子中,由于Function的返回值已经是一个Optional对象,flapMap方法就直接将其返回。

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

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

默认行为及解引用Optional对象

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

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

两个Optional对象的组合

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

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

还假设想要该方法的一个null-安全的版本,它接受两个Optional对象作为参数,返回值是一个Optional<Insurance>对象,如果传入的任何一个参数值为空,它的返回值亦为空。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类提供的特性,有没有更好或更地道的方式来实现这个方法呢?

可以像使用三元操作符那样,无需任何条件判断的结构,以一行语句实现该方法,代码如下。

public Optional<Insurance> nullSafeFindCheapestInsurance(
    					Optional<Person> person, Optional<Car> car) { 
    return person.flatMap( p -> car.map(c -> findCheapestInsurance(p, c))); 
} 

这段代码中,对第一个Optional对象调用flatMap方法,如果它是个空值,传递给它的Lambda表达式不会执行,这次调用会直接返回一个空的Optional对象。反之,如果person对象存在,这次调用就会将其作为函数Function的输入,并按照与flatMap方法的约定返回一个Optional<Insurance>对象。这个函数的函数体会对第二个Optional对象执行map操作,如果第二个对象不包含car,函数Function就返回一个空的Optional对象,整个nullSafeFindCheapestInsuranc方法的返回值也是一个空的Optional对象。最后,如果personcar对象都存在,作为参数传递给map方法的Lambda表达式能够使用这两个值安全地调用原始的findCheapestInsurance方法,完成期望的操作。

Optional类和Stream接口的相似之处远不止mapflatMap这两个方法。还有第三个方法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类中的方法进行了分类和概括。

方法描述
empty返回一个空的Optional实例
filter如果值存在并且满足提供的谓词,就返回包含该值的Optional对象;否则返回一个空的Optional对象
flatMap如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象
get如果该值存在,将该值用Optional封装返回,否则抛出一个NoSuchElementException异常
ifPresent如果值存在,就执行使用该值的方法调用,否则什么也不做
isPresent如果值存在就返回true,否则返回false
map如果值存在,就对该值执行提供的mapping函数调用
of将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常
ofNullable将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象
orElse如果有值则将其返回,否则返回一个默认值
orElseGet如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值
orElseThrow如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常

使用Optional的实战示例

为了保持后向兼容性,我们很难对老的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的对比

由于某种原因,函数无法返回某个值,这时除了返回nullJava 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 { 
        //如果String能转换为对应的Integer,将其封装在Optioal对象中返回 
        return Optional.of(Integer.parseInt(s)); 	
    }catch (NumberFormatException e) { 
        //否则返回一个空的Optional对象 
        return Optional.empty();	
    } 
}

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

Stream对象一样,Optional也提供了类似的基础类型——OptionalIntOptionalLong以及OptionalDouble——所以上面代码中的方法可以不返回Optional<Integer>,而是直接返回一个OptionalInt类型的对象。之前讨论过使用基础类型Stream的场景,尤其是如果Stream对象包含了大量元素,出于性能的考量,使用基础类型是不错的选择,但对Optional对象而言,这个理由就不成立了,因为Optional对象最多只包含一个值。

不推荐使用基础类型的Optional,因为基础类型的Optional不支持mapflatMap以及filter方法,而这些却是Optional类最有用的方法。此外,与Stream一样,Optional对象无法由基础类型的Optional组合构成,所以,举例而言,如果前面的代码中返回的是OptionalInt类型的对象,就不能将其作为方法引用传递给另一个Optional对象的flatMap方法。

把所有内容整合起来

为了展示之前介绍过的Optional类的各种方法整合在一起的威力,假设需要向程序传递一些属性。为了举例以及测试开发的代码,创建了一些示例属性,如下所示:

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")); 

这些断言反映了初始的需求:如果属性是areadDuration方法返回5,因为该属性对应的字符串能映射到一个正数;对于属性b,方法的返回值是0,因为它对应的值不是一个数字;对于c,方法的返回值是0,因为虽然它对应的值是个数字,不过它是个负数;对于d,方法的返回值是0,因为并不存在该名称对应的属性。让我们以命令式编程的方式实现满足这些需求的方法,代码如下所示。

public int readDuration(Properties props, String name) { 
    String value = props.getProperty(name); 
    if (value!= null) { 	//确保名称对应的属性存在
        try { 
            int i = Integer.parseInt(value); 	//将String属性转换为数字类型 
            if (i > 0) {	//检查返回的数字是否为正数 
                return i; 
            } 
        } catch (NumberFormatException nfe) { } 
    } 
    return 0; 	//如果前述的条件都不满足,返回0 
} 

你可能已经预见,最终的实现既复杂又不具备可读性,呈现为多个由if语句及try/catch块构成的嵌套条件。
运用异常与Optional的对比部分的代码中提供的工具方法,可以通过一条精炼的语句重构上面代码中的方法。

如果需要访问的属性值不存在,Properties.getProperty(String)方法的返回值就是一个null,使用ofNullable工厂方法非常轻易地就能把该值转换为Optional对象。接着,可以向它的flatMap方法传递异常与Optional的对比部分的代码中实现的OptionalUtility.stringToInt方法的引用,将Optional<String>转换为Optional<Integer>。最后,非常轻易地就可以过滤掉负数。这种方式下,如果任何一个操作返回一个空的Optional对象,该方法都会返回orElse方法设置的默认值0;否则就返回封装在Optional对象中的正整数。下面就是这段简化的实现:

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

使用OptionalStream时的那些通用模式都是对数据库查询过程的反思,查询时,多种操作会被串接在一起执行。

使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。

使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值