转载

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 机构。

Java8 中的 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"));

}
复制代码
原文  https://juejin.im/post/5d90487fe51d45781420fb5a
正文到此结束
Loading...