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

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实战》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容