美文网首页其他零散知识点map list详解
Java8学习笔记之应用Optional的几种模式

Java8学习笔记之应用Optional的几种模式

作者: 夏与清风 | 来源:发表于2019-08-02 12:02 被阅读0次

    1、创建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)可接受null的Optional

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

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

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

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

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

    Optional<Insurance> optInsurance = Optional.ofNullable(insurance);

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

    map操作会将提供的函数应用于流的每个元素,你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。

    Stream和Optional的map方法对比

    3、使用flatMap链接Optional对象

    两层的optional对象

    flatMap方法:使用流时,flatMap方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,终形成一个新的流的流。flagMap会用流的内容替换每个新生成的流。

    Stream和Optional的flagMap方法对比

    传递给流的flatMap方法会将每个正方形转换为另一个流中的两个三角形。那么,map操作的结果就包含有三个新的流,每一个流包含两个三角形,但flatMap方法会将这种两层的流合并为一个包含六个三角形的单一流。

    传递给optional的flatMap方法的函数会将原始包含正方形的optional对象转换为包含三角形的optional对象。如果将该方法传递给map方法,结果会是一个Optional对象,而这个Optional对象中包含了三角形;但flatMap方法会将这种两层的Optional对象转换为包含三角形的单一Optional对象。

    public String getCarInsuranceName(Optional<Person> person) {

        return person.flatMap(Person::getCar)

                                .flatMap(Car::getInsurance)

                                .map(Insurance::getName)

                                .orElse("Unknown"); //如果Optional的结果值为空,设置默认值

    }

    这种方式的优点,它通过类型系统让你的域模型中隐藏的知识显式地体现在代码中。声明方法接受一个Optional参数,或者将结果作为Optional类型返回,让方法的使用者,很清楚地知道它可以接受空值,或者它可能返回一个空值。

    使用Optional解引用串接的Person/Car/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对象,传递给flapMap的Function,就会应用到Person上对其进行处理。上例中,由于Function的返回值已经是一个Optional对象,flapMap方法就直接将其返回。

    第二步,它会将Optional<Car>转换为Optional<Insurance>。

    第三步,会将Optional<Insurance>转化为Optional<String>对象,由于Insurance.getName() 方法的返回类型为String,不需要进行flapMap操作了。

    由于Optional类设计时没特别考虑将其作为类的字段使用,所以它也并未实现Serializable接口。如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。用Optional声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,替代方案是,提供一个能访问声明为Optional、变量值可能缺失的接口,如下:

    public class Person {

        private Car car;

        public Optional<Car> getCarAsOptional() {

            return Optional.ofNullable(car);

        }

    }

    4、默认行为及解引用Optional对象

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

    \bullet get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。除非你非常确定Optional变量一定包含值,否则不要使用这个方法。此外,这种方式即便相对于嵌套式的null检查,也并未体现出多大的改进。

    \bullet orElse(T other),它允许你在Optional对象不包含值时提供一个默认值。

    \bullet orElseGet(Supplier<? extends T> other)是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。如果创建默认值是耗时费力的工作,应该考虑采用这种方式,或者需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。

    \bullet orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法类似, 它们遇到Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。

    \bullet ifPresent(Consumer<? super T>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

    5、两个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对象。最后,如果person和car对象都存在,作为参数传递给map方法的Lambda表达式能够使用这两个值安全地调用原始的findCheapestInsurance方法,完成期望的操作。

    6、使用filter剔除特定的值

    如果经常需要调用某个对象的方法,查看它的某些属性,可以使用Optional对象的filter方法。

    Optional<Insurance> optInsurance = ...;

    optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName()))                     .ifPresent(x -> System.out.println("ok"));

    filter方法接受一个谓词作为参数。如果Optional对象的值存在,并且它符合谓词的条件,filter方法就返回其值;否则它就返回一个空的Optional对象。如果Optional 对象为空,它不做任何操作,反之,它就对Optional对象中包含的值施加谓词操作。如果该操作的结果为true,它不做任何改变,直接返回该Optional对象,否则就将该值过滤掉,将Optional的值置空。

    对Optional对象进行过滤:

    public String getCarInsuranceName(Optional<Person> person, int minAge)

    找出年龄大于或者等于minAge参数的Person所对应的保险公司列表:可以对Optional封装的Person对象进行filter操作,设置相应的条件谓词,即如果person的年龄大于minAge参数的设定值,就返回该值,并将谓词传递给filter方法。

    public String getCarInsuranceName(Optional<Person> person, int minAge) {

        return person.filter(p -> p.getAge() >= minAge)

                .flatMap(Person::getCar)

                .flatMap(Car::getInsurance)

                .map(Insurance::getName)

                .orElse("Unknown");

    }

    Optional类的方法说明:

    \bullet empty:返回一个空的Optional实例。

    \bullet filter:如果值存在并且满足提供的谓词,就返回包含该值的Optional对象;否则返回一个空的Optional对象。

    \bullet flatMap:如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。

    \bullet get:如果该值存在,将该值用Optional封装返回,否则抛出一个NoSuchElementException异常。

    \bullet ifPresent:如果值存在,就执行使用该值的方法调用,否则什么也不做。

    \bullet isPresent:如果值存在就返回true,否则返回false。

    \bullet map:如果值存在,就对该值执行提供的mapping函数调用。

    \bullet of:将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常。

    \bullet ofNullable:将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象。

    \bullet orElse:如果有值则将其返回,否则返回一个默认值。

    \bullet orElseGet:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值。

    \bullet orElseThrow:如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常。

    --参考文献《Java8实战》

    相关文章

      网友评论

        本文标题:Java8学习笔记之应用Optional的几种模式

        本文链接:https://www.haomeiwen.com/subject/bqugrctx.html