Java 8新特性(三):Optional类(转载)

原文地址

在上一篇介绍Stream流式数据处理的文章中提到了Optional类,这是Java 8新增的一个类,用以解决程序中常见的NullPointerException异常问题。本篇文章将详细介绍Optional类,以及如何用它消除代码中的null检查。

避免使用null检查

作为Java开发人员,几乎所有人都遇到过NullPointerException异常,大多数人遇到NullPointerException异常时都会在异常出现的地方加上if代码块来判断值不为空,比如下面的代码:

public void bindUserToRole(User user) {
    if (user != null) {
        String roleId = user.getRoleId();
        if (roleId != null) {
            Role role = roleDao.findOne(roleId);
            if (role != null) {
                role.setUserId(user.getUserId());
                roleDao.save(role);
            }
        }
    }
}

这是比较普遍的做法,为了避免出现NullPointerException异常,手动对可能为null值进行了处理,不过代码看起来非常糟糕,业务逻辑被淹没在if逻辑判断中,也许下面的代码看起来可读性稍好一些:

public String bindUserToRole(User user) {
    if (user == null) {
        return;
    }

    String roleId = user.getRoleId();
    if (roleId == null) {
        return;
    }

    Role = roleDao.findOne(roleId);
    if (role != null) {
        role.setUserId(user.getUserId());
        roleDao.save(role);
    }
}

上面的代码避免了深层的if语句嵌套,但本质上是一样的,方法内有三个不同的返回点,出错后调试也不容易,因为你不知道是那个值导致了NullPointerException异常。

基于上面的原因,Java 8中引入了一个新的类Optional,用以避免使用null值引发的种种问题。

Optional类

java.util.Optional<T>类是一个封装了Optional值的容器对象,Optional值可以为null,如果值存在,调用isPresent()方法返回true,调用get()方法可以获取值。

创建Optional对象

Optional类提供类三个方法用于实例化一个Optional对象,它们分别为empty()、of()、ofNullable(),这三个方法都是静态方法,可以直接调用。

1、empty()方法用于创建一个没有值的Optional对象:

Optional<String> emptyOpt = Optional.empty();

empty()方法创建的对象没有值,如果对emptyOpt变量调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。

2、of()方法使用一个非空的值创建Optional对象:

String str = "Hello World";
Optional<String> notNullOpt = Optional.of(str);

3、ofNullable()方法接收一个可以为null的值:

Optional<String> nullableOpt = Optional.ofNullable(str);

如果str的值为null,得到的nullableOpt是一个没有值的Optional对象。

提取Optional对象中的值

如果我们要获取User对象中的roleId属性值,常见的方式是直接获取:

String roleId = null;
if (user != null) {
    roleId = user.getRoleId();
}

使用Optional中提供的map()方法可以以更简单的方式实现:

Optional<User> userOpt = Optional.ofNullable(user);
Optional<String> roleIdOpt = userOpt.map(User::getRoleId);

使用orElse()方法获取值

Optional类还包含其他方法用于获取值,这些方法分别为:

  • orElse():如果有值就返回,否则返回一个给定的值作为默认值;
  • orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;
  • orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。

下面来看看这三个方法的具体用法:

String str = "Hello World";
Optional<String> strOpt = Optional.of(str);
String orElseResult = strOpt.orElse("Hello Shanghai");
String orElseGet = strOpt.orElseGet(() -> "Hello Shanghai");
String orElseThrow = strOpt.orElseThrow(
        () -> new IllegalArgumentException("Argument 'str' cannot be null or blank."));

此外,Optional类还提供了一个ifPresent()方法,该方法接收一个Consumer<? super T>函数式接口,一般用于将信息打印到控制台:

Optional<String> strOpt = Optional.of("Hello World");
strOpt.ifPresent(System.out::println);

使用filter()方法过滤

Optional<String> optional = Optional.of("lw900925@163.com");
optional = optional.filter(str -> str.contains("164"));

在上面的代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。

如何正确使用Optional

通过上面的例子可以看出,Optional类可以优雅的避免NullPointerException带来的各种问题,不过,你是否真正掌握了Optional的用法?假设你试图使用Optional来避免可能出现的NullPointerException异常,编写了如下代码:

Optional<User> userOpt = Optional.ofNullable(user);
if (userOpt.isPresent()) {
    User user = userOpt.get();
    // do something...
} else {
    // do something...
}

坦白说,上面的代码与我们之前的使用if语句判断空值没有任何区别,没有起到Optional的正真作用:

if (user != null) {
    // do something...
} else {
    // do something...
}

当我们从之前版本切换到Java 8的时候,不应该还按照之前的思维方式处理null值,Java 8提倡函数式编程,新增的许多API都可以用函数式编程表示,Optional类也是其中之一。这里有几条关于Optional使用的建议:

  • 1、尽量避免在程序中直接调用Optional对象的get()和isPresent()方法;
  • 2、避免使用Optional类型声明实体类的属性;

第一条建议中直接调用get()方法是很危险的做法,如果Optional的值为空,那么毫无疑问会抛出NullPointerException异常,而为了调用get()方法而使用isPresent()方法作为空值检查,这种做法与传统的用if语句块做空值检查没有任何区别。

第二条建议避免使用Optional作为实体类的属性,它在设计的时候就没有考虑过用来作为类的属性,如果你查看Optional的源代码,你会发现它没有实现java.io.Serializable接口,这在某些情况下是很重要的(比如你的项目中使用了某些序列化框架),使用了Optional作为实体类的属性,意味着他们不能被序列化。

下面我们通过一些例子讲解Optional的正确用法:

1、正确创建Optional对象‘

上面提到创建Optional对象有三个方法,empty()方法比较简单,没什么特别要说明的。主要是of()和ofNullable()方法。当你很确定一个对象不可能为null的时候,应该使用of()方法,否则,尽可能使用ofNullable()方法,比如:

public static void method(Role role) {
    // 当Optional的值通过常量获得或者通过关键字new初始化,可以直接使用of()方法
    Optional<String> strOpt = Optional.of("Hello World");
    Optional<User> userOpt = Optional.of(new User());

    // 方法参数中role值不确定是否为null,使用ofNullable()方法创建
    Optional<Role> roleOpt = Optional.ofNullable(role);
}

2、orElse()方法的使用

return str != null ? str : "Hello World"

上面的代码表示判断字符串str是否为空,不为空就返回,否则,返回一个常量。使用Optional类可以表示为:

return strOpt.orElse("Hello World")

3、简化if-else

User user = ...
if (user != null) {
    String userName = user.getUserName();
    if (userName != null) {
        return userName.toUpperCase();
    } else {
        return null;
    }
} else {
    return null;
}

上面的代码可以简化成:

User user = ...
Optional<User> userOpt = Optional.ofNullable(user);

return userOpt.map(User::getUserName)
            .map(String::toUpperCase)
            .orElse(null);

总结一下,新的Optional类让我们可以以函数式编程的方式处理null值,抛弃了Java 8之前需要嵌套大量if-else代码块,使代码可读性有了很大的提高。下一篇文章将介绍Java 8中新添加的日期API。

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

推荐阅读更多精彩内容