Java8-Lambda编程[3] Optional接口

引言

Optional意为可选,我们前面已经提及过,主要是为了替代null的使用,避免空指针异常(NullPointerException)的出现。譬如定义下面一个类A:

class A {
    private String name;

    public String getName() {
        return name;
    }

如果我调用它的getName方法获取name字段并进行后续操作,就将会发生异常。因为我并没有为A写一个构造方法,所以name字段将会为空,如果此时对结果进行操作,比如调用length方法,将会抛出一个空指针异常,这就会很恼人。在没有Optional接口之前,我们常用的避免异常发生的方法是添加一个硬性检查。

例4.0:
    String name=new A().getName;
    if(name!=null)
        System.out.println(name.length());

空城流云 Optional接口

上述代码并不是一个美观的写法,因为总是要在执行命令前进行检查,就好像套了一个try-catch块一样冗余丑陋。为了可以消除这样的样板代码,J8提供了Optional接口,利用它可以写出函数式风格的代码来,比如下面的变形。

例4.1:
    Optional<A> oa=Optional.ofNullable(new A());
    oa.map(A::getName)
      .map(String::length)
      .ifPresent(System.out::println);

这样的写法看起来就好像是我们最先学过的Stream类的风格,连方法名都很相似。我们一个一个来分析,最上面的ofNullable工厂方法负责生产一个Optional<A>类型的对象,这样的方法一共有三个,除了ofNullable还有of、和empty。of方法接受一个A类型的参数,并将其装箱成Optional<A>类型的对象,如果传进来的参数为空,则会直接抛出一个NullPointerException,确保被装箱的对象非空(这里我姑且乱用“装箱”和“拆箱”这两个术语,因为Optional对象很像是把一个对象放到了箱子里,当然它也可能会是一个空箱子)。empty则正好相反,调用后直接生成一个内容为空的Optional对象,这使得该对象的功能有点像null,但实际上差别很大,要不然Optional类还有什么意义呢?最后是我们上面使用的ofNullable方法,顾名思义,就是可以传进来一个可空的A类型参数,结合了上面两个方法的功能,相比于of方法,此方法在传入值为null为参数时并不会抛出异常,而是直接生成一个空内容的Optional<A>对象。

生成Optional后,我们又继续调用了一个为map的方法,它与Stream中的map方法很相似,都是对泛型对象进行映射。上例中我们按照A::getName方法所对应的映射规则,将一个Optional<A>类型的对象映射成了一个Optional<String>类型的对象,紧接着又将其映射成了一个Optional<Integer>类型的对象。除了map外,Optional还有filter和flatmap两个方法与Stream很相似。filter很好理解,传入一个Predicate类型的参数来对Optional对象进行所谓的筛选,如果符合条件就保持不变返回对象本身,否则将会返回一个空内容的Optional。flatMap也很好理解,在Stream中它负责将映射后的对象整合成一个流,而在这里,它的作用是将Optional对象拆成一层包装的形式,比如对于下面这个类:

class B {
    private A a;

    public Optional<A> getA() {
        return Optional.of(a);
    }

如果我们想要获取new B().getA().getName(),直接调用两次map方法是不行的。因为我们为了防止成员a为null,进而导致空指针异常,使用了Optional<A>类型的返回值。因此直接对Optional<B>对象按照getA方法进行映射操作,会得到一个Option<Option<A>>类型的对象,拆箱后的结果是一个Optional<A>而不是A,所以无法按照getName方法进行映射。为此我们需要使用flatMap,此方法会自动进行拆箱,得到一个只有一层包装的Optional<A>对象。譬如下面的代码,通过flatMap及后续方法,获取含有Name为字母“a”开头字符串的A字段的B对象。

例4.2:
    String s = Optional.ofNullable(new B())
            .flatMap(B::getA)
            .map(A::getName)
            .filter(str->str.startsWith("a"))
            .get();

这里我们先通过flatMap方法获取了Optional<A>对象,在通过map方法获取了Optional<String>对象,然后通过filter方法进行判断,如果箱内的字符串不以字母“a”开头,则返回一个空箱。上述代码的最后我们调用了一个get方法,这是Optional类最常用的一个方法,直接获取箱内的值,如果是空箱,则会返回null。如果我们想要对s进行标准输出,那么我们会收到一条无此元素异常(NoSuchElementException)而不是空指针异常,不过是换了个异常名字,这使得Optional类看起来好像没毛用,为了避免异常我们还是要进行“!=null”形式的判断,不过这次可以直接用Optional的方法来执行。

例4.3:
    Optional<String> os = Optional.ofNullable(new B())
            .flatMap(B::getA)
            .map(A::getName)
            .filter(str->str.startsWith("a"))
            .ifPresent(System.out::println);

上述代码中最后调用的就是我们一直留着没讲的ifPresent方法,这个方法看名字也很好理解,它传入一个Consumer类型的参数,如果Optional对象存在内容,则消费里面的对象,即对其执行Consumer中对应的操作。此外还有一个类似的isPresent方法,用于判断箱内是否为空。

Schrödinger's null

有些人会类比于Stream的两类方法,将map、flatMap、filter归为一类,get、ifPresent、isPresent归为一类,因为前者返回的是Optional类型的对象,可以进行级联,而后者的返回值则是其他类型,只能用在级联末尾。他们确实都很相似,包括上一章的Collector与后面要讲的CompleteFuture在内,在设计上都是想要体现一种Java8独特的函数式编程风格。

get方法很像Stream中的collect方法,会将Stream<T>中的元素收集为T收集或T数组,get则会将Optinal<T>转化为T对象,前提是Optinal对象内部的T对象不为null。那么一旦T对象为null会怎样呢,前面我们已经说过get方法会抛出无此元素异常,这让Optional类显得很鸡肋。实际上get方法还有几个兄弟,我一直藏着没讲,它们的名字叫做orElse、orElseGet、orElseThrow。orElse方法传入一个T类型的参数,当内容为空时返回该参数作为缺省值,orElseGet则传入一个Supplier对象来提供缺省值。orElseThrow则传入一个Supplier用来生成要抛出的异常,有了它我们对于空值就可以不再满足于默认的空指针异常以及get方法提供的无此元素异常,而可以定制自己的异常,这听起来是不是很疯狂呢?毕竟有时候我们想要体验J8的新功能,又需要抛出定制异常的获取错误信息,那么就可以把二者结合起来,使用Optional的orElseThrow方法来使代码看起来更加有格调。

大试牛刀

学习了Optional接口所有的方法,我们就来实际运用一下,对下面的代码进行一次变形。直接在第一行就写return会给人带来非常爽快的感觉,下面的代码看起来就像是函数式编程语言中的闭包。

例4.4:
//命令式
public String getFirst(String s) {
    if (s != null)
        if (s.length() != 0)
            return s.substring(0, 1);
    return "空";
}
//函数式
public String _getFirst(String s){
    return Optional.ofNullable(s)
            .filter(str->str.length()!=0)
            .map(str->str.substring(0,1))
            .orElse("空");
}

小结 可选的Optional

说了这么多,可能还是有很多人想问,这个Optional到底有什么用呢,好像很鸡肋不用也罢。就好比get方法,和我直接进行非空检查相比能简洁到哪里去呢,还要在外面在套一层Optional壳,使代码看起来更加繁琐更加费解,而且不断地拆箱装箱也很烦人,还不如用我以前学过的习惯性写法。这个东西其实见仁见智,就好比匿名内部类与Lambda表达式孰优孰劣,不同人有着不同的看法。函数式风格的代码其实是一种思想,或者在这里也可以看成是一种设计模式,比起以前那种冗长的命令式代码,函数式代码看起来更加清晰、玄妙。至于这种风格是否能够长远的发展下去,甚至广泛取代传统写法,历史自有定论,我等只需将这些新思想传播开来,但并不会强制要求所有人都要接受,用与不用还是在于程序员自身的偏好。

最后让我们再来看一串代码,将两个Optional拆箱运算后再返回一个Optional对象,这段代码我就不讲解了,有兴趣的读者可以玩味一番。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 引言 Stream意为流,是Lambda编程中的一个重要角色。Stream类主要用于对收集类、数组、文件的迭代,以...
    斯特的简书阅读 429评论 0 0
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,208评论 1 2
  • Optional 本章内容 如何为缺失的值建模 Optional 类 应用Optional的几种模式 使用Opti...
    追憶逝水年華阅读 1,782评论 0 0
  • 本文获得Stackify授权翻译发表,转载需要注明来自公众号EAWorld。 作者:EUGEN PARASCHIV...
    72a1f772fe47阅读 11,673评论 3 7