Java8 中的 Optional 类的基本使用

Java8 引入了一个十分有趣的 Optional 类它主要是为了解决臭名昭著的空指针异常(NullPointerException)。当我们对对象的属性进行检查,判断它的值是否为期望的格式,最终却发现我们查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的 NullPointerException 异常。

抛砖

我们来看一个简单的实例:

String address = world.getCountry.getCity.getName;
复制代码

在得到地址之前,需要对各个类进行检查防止出现空指针异常:

public String getAddress(World world){
        if (world != null){
            Country country = world.getCountry();
            if (country!=null){
                City city = country.getCity();
                if (city != null){
                    return city.getName();
                }
            }
        }

        return "unknown";
    }
复制代码

可以看到上面的检查有多么繁杂,代码中充斥着空检查,可读性糟糕透顶。

Optional 类入门

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

应用 Optional 的几种模式

创建 Optional 对象实例

可以创建一个空的 Optional 对象实例

@Test(expected = NoSuchElementException.class)
    public void createOptionalObject(){
        Optional<String> country = Optional.empty();
        country.get();
    }
复制代码

毫无疑问,当我们调用 get() 方法会报 NoSuchElementException 异常

还可以使用 of() 和 ofNullable() 方法创建包含值的 Optioanal 实例,区别在于如果将 null 当作参数传进去 of() 会报空指针异常,所以对象可能存在或者不存在,应该使用 ofNullable()

@Test
    public void createOptionalObject(){
        Optional<String> country = Optional.of("中国");
        Optional<String> city = Optional.ofNullable("上海");
        Optional<String> world = Optional.ofNullable(null);
        //下面会报空指针异常
        Optional<String> province = Optional.of(null);
    }
复制代码

如何获取Optional变量中的值 ?Optional 提供了一个 get() 方法。不过 get方法在遭遇到空的Optional对象时也会抛出异常,所以不按照约定的方式使用它,又会让我们再度陷入由null引起的代码维护的梦魇。

访问 Optional 对象的值

从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

@Test
    public void getOptionalObject(){
        String country = "China"
        Optional<String> countryName = Optional.ofNullable(country);
        
        Assert.assertEquals("China",countryName.get());
    }
复制代码

当然这个方法会在值为null时抛出异常,要避免异常,首先要进行检查

 @Test
    public void getOptionalObject(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        Assert.assertTrue(sample.isPresent());

        Assert.assertEquals(city.getName(),sample.get().getName());
    }
复制代码

检查是否有值还有另外一个方法 ifPresent(),该方法除了检查还会传入一个 Consumer(消费者) 参数,如果对象不是空的,就会执行传入的 Lambda 表达式

@Test
    public void getOptionalObject(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        sample.ifPresent(c -> Assert.assertEquals(city.getName(),sample.get().getName()));
    }
复制代码

如果对象不为空则为执行断言

返回默认值

Optional 提供了 API 用以返回对象值,或者在对象为空的时候返回默认值

@Test
    public void getOptionalObject(){
        City city = null;
        City city1 = new City("ShangHai");
        City sample = Optional.ofNullable(city).orElse(city1);
        Assert.assertEquals(city1.getName(),sample.getName());
    }
复制代码

第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:

City sample = Optional.ofNullable(city).orElseGet(() -> city1);
复制代码

返回异常

Optional 还定义了 orElseThrow() API 它会在对象为空时抛出异常

@Test(expected = IllegalArgumentException.class)
    public void throwOptionalException(){
        City city = null;
        City sample = Optional.ofNullable(city).orElseThrow(() -> new IllegalArgumentException());
    }
复制代码

city 为空所以会抛出 IllegalArgumentException

这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。

使用 Optional 的实战实例

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

从对象中提取信息是一种比较常见的模式,为了支持这种模式,Optional提供了一个map方法。它的工作方式如下:

@Test
    public void getCityName(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        Optional<String> name = sample.map(City::getName);
    }
复制代码

map 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中,这就使对返回值进行链试调用的操作成为可能,那是不是就可以对之前的代码进行重构呢?

public Optional<String> getCityName(World world){

        Optional<World> real = Optional.ofNullable(world);
        Optional<String> name =
                real.map(World::getCountry)
                    .map(Country::getCity)
                    .map(City::getName);

        return name;
    }
复制代码

但是这段代码无法通过编译,real.map(World::getCountry) 返回的是 Optional 的实例这个没有问题,但是后面继续调用map产生的就是 Optional<Optional>类型的对象。说明你遭遇了嵌套式的 Optional 机构。

optional

两层Optional对象结构

使用 flatMap 链接 Optional 对象

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

public Optional<String> getCityName(World world){

        Optional<World> real = Optional.ofNullable(world);
        Optional<String> name =
                real.flagMap(World::getCountry)
                    .flagMap(Country::getCity)
                    .map(City::getName);

        return name;
    }
复制代码

使用 filter 剔除特定的值

你经常需要调用某个对象的方法,那么你首先要检查对象是否为NULL

public void filterCity(City city){

    Optional<City> real = Optional.ofNullable(city);
    real.filter(c -> c!=null && "ShangHai"
            .equals(c.getName()))
            .ifPresent(x -> System.out.println("ok"));

}
复制代码

小结

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

猜你喜欢

转载自juejin.im/post/5d90487fe51d45781420fb5a