Java Optional 类的使用

在java开发过程中经常会遇到空指针异常NullPointerExceptions。
NullPointerExceptions是jvm在运行时抛出的RuntimeException。程序中的对象为空的检查容易被开发人员忽略,导致代码中出现严重错误。
在Java 8版本中引入了 Optional<T> 这个新类帮助开发人员正确处理对象为空的情况。Optional表示可选的,在其他编程语言也有类似的设计,比如:Scala 有Optional[T]类,Haskell 有 Maybe 类。

什么是 Optional?

Optional是可能不存在的对象值的容器类型,一个Optional对象不一定包含有值。
下面代码从数据库中获取具有给定id的用户详细信息并返回用户信息对象:

User findUserById(String userId) { ... };

如果数据库中不存在该userId,则方法返回null。我们看看以下调用端代码:

        User user = findUserById("667290");
        System.out.println("User's Name = " + user.getName());

非常常见的NullPointerException异常。开发人员忘记在代码中添加用户对象是否为空的检查。如果数据库中不存在userId,则上述代码段将抛出一个NullPointerException。
我们看看用Optional类处理在这里遇到NullPointerException的异常:

Optional<User> findUserById(String userId) { ... };

通过从方法返回Optional<User>对象,我们已经向该方法的调用端表明,可能没有具有给定用户id的用户信息对象。现在这个方法的调用端被显式地强制处理这个结果。
客户端代码现在可以改写为:

Optional<User> optional = findUserById("667290");
optional.ifPresent(user -> {
    System.out.println("User's name = " + user.getName());   
})

一旦有了Optional<T>对象,就可以使用各种实用方法来处理它。
上面代码中的ifPresent() 方法的逻辑是,在用户对象存在时调用传入的lambda 表达式,否则不做任何处理。系统现在强制客户端在其代码中写入Optional<T>对象的检查逻辑。

创建一个 Optional 实例

1. 创建空的Optional<T>

Optional<User> user = Optional.empty();

空的Optional实例用于在对象值缺失的情况。

2. 创建非空的Optional<T>

User user = new User("667290", "Jim");
Optional<User> userOptional = Optional.of(user);

如果提供给Optional.of()的参数为空,则它将立即抛出一个NullPointerException,并且不会创建Optional对象。

3. 创建值可能为空也可能不为空的Optional<T>

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

如果传递给Optional.ofNullable()的参数为非空,则返回包含指定值的Optional对象,否则返回空的Optional对象。

检查值是否存在

1.isPresent()

如果Optional对象包含非空值,则isPresent()方法返回true,否则返回false 。

if(optional.isPresent()) {
    // value is present inside Optional
    System.out.println("Value found - " + optional.get());
} else {
    // value is absent
    System.out.println("Optional is empty");
}      

2.ifPresent()

ifPresent() 方法允许传递在Optional对象中存在值时执行的Consumer接口的实现。如果Optional对象不存在值时什么也不做。

optional.ifPresent(value -> {
    System.out.println("Value found - " + value);
});

注意,上述代码ifPresent()方法提供了lambda表达式。这使得代码更加可读和简洁。

使用get() 方法取值

Optional对象的get() 返回其值, 如不存在则抛出NoSuchElementException异常。

User user = optional.get()

我们要避免在没有先检查值是否存在的情况下对Optional使用get()方法,因为如果值不存在,它将引发异常。

使用orElse()返回默认值

orElse() 方法在 Optional值不存在时返回默认值,考虑以下传统的代码处理场景:

// return "Unknown User" if user is null
User finalUser = (user != null) ? user : new User("0", "Unknown User");

现在我们可以使用 Optional的 orElse() 方法实现上述逻辑:

// return "Unknown User" if user is null
User finalUser = optionalUser.orElse(new User("0", "Unknown User"));

使用orElseGet()返回默认值

和orElse()方法不同, orElse()在Optional值为空时是直接返回默认值,而 orElseGet() 允许你传入一个Supplier接口实现,该接口实现提供自定义默认值的功能。

User finalUser = optionalUser.orElseGet(() -> {
    return new User("0", "Unknown User");
});

在值为空时引发异常

可以用orElseThrow() 在Optional值为空时抛出异常。一个典型的业务场景是,在REST API接口需要在给定参数查询的对象不存在时返回自定义的 ResourceNotFoundException 异常。

@GetMapping("/users/{userId}")
public User getUser(@PathVariable("userId") String userId) {
    return userRepository.findByUserId(userId).orElseThrow(
            () -> new ResourceNotFoundException("User not found with userId " + userId);
    );
}

使用filter() 方法筛选值

假如有一个Optional<User>对象。我们要检查此人的性别,如果是男性,就调用方法执行某种逻辑处理。传统的处理方式:

if(user != null && user.getGender().equalsIgnoreCase("MALE")) {
    // call a function
}

现在我们用Optional和filter来实现相同的功能:

userOptional.filter(user -> user.getGender().equalsIgnoreCase("MALE"))
.ifPresent(() -> {
    // Your function
})

filter()方法接受谓词作为参数。如果Optional包含非空值,且该值与给定谓词匹配,则filter()方法返回具有该值的Optional对象,否则返回空的Optional对象。

因此,仅当Optional<User>包含用户且用户是男性时,才会调用上述示例中ifPresent()中的代码。

使用map()方法提取和转换值

假设想要获取用户的地址(如果该地址存在),如果该用户来自指定的地方,则打印该地址。

在User对象有getAddress() 方法:

Address getAddress() {
    return this.address;
}

传统的做法:

if(user != null) {
    Address address = user.getAddress();
    if(address != null && address.getCountry().equalsIgnoreCase("India")) {
            System.out.println("User belongs to India");
    }
}

现在我们使用map()方法实现同样的功能:

userOptional.map(User::getAddress)
.filter(address -> address.getCountry().equalsIgnoreCase("India"))
.ifPresent(() -> {
    System.out.println("User belongs to India");
});

上面的代码很简洁,一起来详细理解一下:

//1.使用map()方法提取用户地址。
Optional<Address> addressOptional = userOptional.map(User::getAddress)
//2.过滤地址
Optional<Address> indianAddressOptional = addressOptional.filter(address -> address.getCountry().equalsIgnoreCase("India"));
// 3.打印输出结果
indianAddressOptional.ifPresent(() -> {
    System.out.println("User belongs to India");
});

在上面的示例中, map() 方法在以下情况下会返回空的Optional:1.userOptional的用户信息不存在. 2. 用户存在但 getAdderess() 返回null。

其他情况返回包含用户地址的 Optional<Address> 对象.

使用flatMap()方法转换值

我们再讨论一下前面 map() 方法的例子. 你可能会问,如果用户的地址可以为空,那么为什么不从getAddress()方法返回一个Optional<Address>对象而是Address对象?

我们来看看当 getAddress() 返回Optional<Address>时上述代码:

Optional<Address> addressOptional = userOptional.map(User::getAddress)

因getAddress() 返回了Optional<Address>, 所以 userOptional.map()将会返回Optional<Optional<Address>>

Optional<Optional<Address>> addressOptional = userOptional.map(User::getAddress)

这里不需要嵌套的 Optional,可以用 flatMap() 来展开:

Optional<Address> addressOptional = userOptional.flatMap(User::getAddress)

如果映射函数返回一个Optional对象,那么就要使用flatMap()而不是map()。

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