一起来学Java8(六)——Optional

Optional类是Java8新增的一个类,其作用可以有效避免空指针异常。

Optional类的代码比较简单,很容易就能看懂,其实质就是一个辅助类,把需要处理的对象做了一层包装,然后再使用Optional中的方法时,可以有效得判断处理对象是否为空,从而做出正确判断。

接下来我们看下如何使用Optional。

创建Optional

创建Optional有3种方式:

  • Optional.empty() 返回一个空的Optional
  • Optional.of(不为null的对象)
  • Optional.ofNullable(可以为null的对象)

如果能够确保入参一定不为null,可以用Optional.of,因为在Optional.of内部会判断是否为null,如果是null则抛出异常。

如果不太确定入参是否为null,可以用Optional.ofNullable

对象创建好了,接下来看看如何使用。

isPresent和ifPresent

isPresent()方法判断处理对象是否为null,不为null返回true,源码如下:

public boolean isPresent() {
    return value != null;
}

ifPresent方法有一个入参ifPresent(Consumer<? super T> consumer),它的意思是如果对象不为null,则运行consumer进行处理,有点类似回调函数。

String s = "hello";     
Optional<String> optional = Optional.of(s);
if(optional.isPresent()) {
    System.out.println("the value is " + optional.get());
}

同样可以写成:

optional.ifPresent((val) -> {
    System.out.println("the value is " + val);
});

filter

filter是对处理对象进行判断,如果判断为true,则返回当前Optional,如果为false则返回一个空的Optional对象,其源码如下:

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

filter方法有个参数:Predicate,这是一个函数式接口,因此我们可以使用Lambda表达式来处理。

String s = "hello";     
Optional<String> optional = Optional.of(s);

boolean exist = optional
    .filter(val -> "hello1".equals(val))
    .isPresent();
System.out.println(exist); // false

map和flatMap

map方法的作用可以简单理解成从处理对象中取出其它对象,然后返回一个新的Optional。如下代码所示:

public class OptionalMapTest {
    static class Goods {
        private String goodsName;
        private Company company;

        ...getter setter
    }

    static class Company {
        private String companyName;

        ...getter setter
    }

    public static void main(String[] args) {
        Company company = new Company();
        company.setCompanyName("Apple");
        Goods goods = new Goods();
        goods.setGoodsName("iphoneX");
        goods.setCompany(company);
        
        Optional<Goods> optional = Optional.of(goods);
        String companyName = optional
                // 从goods中取出Company,返回一个新的Optional<Company>
                .map(goodsObj -> goodsObj.getCompany())
                // 从company中取出companyName,返回一个新的Optional<String>
                .map(companyObj -> companyObj.getCompanyName())
                // 得到companyName
                .get();
        System.out.println(companyName);
    }
    
}

什么情况下该使用flatMap呢,我们把Goods中的的Company对象改成Optional<Company>

static class Goods {
    private String goodsName;
    private Optional<Company> company;

    ...getter setter
}

此时下面这段代码会编译报错

String companyName = optional
    // 从goods中取出Company,返回一个新的Optional<Company>
    .map(goodsObj -> goodsObj.getCompany()) // !!这里会报错
    // 从company中取出companyName,返回一个新的Optional<String>
    .map(companyObj -> companyObj.getCompanyName())
    // 得到companyName
    .get();

主要是这行代码optional.map(goodsObj -> goodsObj.getCompany())。因为此时返回的是一个Optional<Optional<Company>>对象。

而我们需要的是Optional<Company>对象,这个时候就应该用到flatMap了,只要把optional.map(goodsObj -> goodsObj.getCompany())改成optional.flatMap(goodsObj -> goodsObj.getCompany())即可。

String companyName = optional
    // 从goods中取出Company,返回一个新的Optional<Company>
    .flatMap(goodsObj -> goodsObj.getCompany())
    // 从company中取出companyName,返回一个新的Optional<String>
    .map(companyObj -> companyObj.getCompanyName())
    // 得到companyName
    .get();

简单的理解就是:

  • optional.map() 会把返回的结果再次放到一个Optional中
  • optional.flatMap() 不会把结果放入放到Optional中,把这个操作交给开发者来处理,让开发者自己返回一个Optional

get,orElse,orElseGet,orElseThrow

  • get():返回被处理的值,如果值为空,则抛出异常
String s = null;
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.get()); // 抛出java.util.NoSuchElementException: No value present

针对这种情况,有几种处理方式

方式1:使用isPresent()

String s = null;
Optional<String> optional = Optional.ofNullable(s);
if (optional.isPresent()) {
    System.out.println(optional.get());
} else {
    System.out.println("默认值");
}

方式2:使用orElse(默认值)

String s = null;
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.orElse("默认值"));

orElse(默认值)的意思是如果Optional中的值为null,则返回给定的默认值。

方式3:使用orElseGet(Supplier)

String s = null;
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.orElseGet(() -> "默认值"));

orElse(Supplier)的意思是如果Optional中的值为null,则执行指定的Supplier接口,由于Supplier是个函数式接口,因此可以使用Lambda表达式代替。

由此看来,方式2和方式3的处理是比较优雅的。

方式2和方式3的区别在于,方式3可以延迟返回,只有值为null的情况下才会触发() -> "默认值",从而避免生成无用对象,方式2不管如何都生成了"默认值"这个字符串对象。下面的例子可以说明:

String s = "1";
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.orElse(getDefault()));

打印:

生成了字符串对象
1

即使Optional中的值不为null,但还是执行了getDefault(),这完全没必要,再来看下使用orElseGet

String s = "1";
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.orElseGet(() -> getDefault()));

打印:1

接着再看下orElseThrow,如果值为null,则直接抛出异常

String s = null;
Optional<String> optional = Optional.ofNullable(s);
System.out.println(optional.orElseThrow(() -> new NullPointerException("不能为空")));

Optional实战

{
    "user": {
        "age": 20
        ,"name": "Jim"
        ,"address": {
            "province": "浙江省"
            ,"postcode": "111111"
        }
   }
}

假设有这样一个json字符串,现在要获取postcode信息。如果不用Optional的话,要写各种if…else语句,还要判断字段是否存在。

String postcode = "unknown";
JSONObject user = jsonObj.getJSONObject("user");
if (user != null) {
    JSONObject address = user.getJSONObject("address");
    if (address != null) {
        String code = address.getString("postcode");
        if (postcode != null) {
            postcode = code;
        }
    }
}
System.out.println(postcode);

但是用Optional可以这样写:

JSONObject jsonObj = JSON.parseObject(json);

String postcode = Optional.ofNullable(jsonObj)
        .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("user")))
        .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("address")))
        .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getString("postcode")))
        .orElse("unknown");

System.out.println(postcode);

注意,这里要使用flatMap,由开发者手动返回一个Optional对象,如果使用map的话则返回Optional<Optional<JSONObject>>

最后一句.orElse("unknown")表示如果一路走下来没有找到值,则返回一个默认值。

Optional的优势是处理嵌套数据结构,如这里的json数据。假如这段json数据结构不是完整的,postcode字段不存在,或者整个address字段都不存在,在无法保证嵌套数据中的值是否存在的情况下,使用Optional是个不错的选择。它都能确保有个正确的返回值。

小节

本篇主要介绍了Optional类的用法,同时演示了如何使用Optional处理嵌套数据。

定期分享技术干货,一起学习,一起进步!

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

推荐阅读更多精彩内容