Java8 中的Optional类详解

在Java8中,引入了一个新的类java.util.Optional 。这个类专门用来解决空引用的问题。有了它,对于一个方法的返回值什么的,都不需要再判断是不是null了。同时,这个类和lambda表达式和函数式编程也可以比较好的整合在一起使用。
这里就来看一下这个类的使用。
知道Scala的同学可以类比一下 scala.Option[T] ,或者Haskell的Data.Maybe。都有类似的功能。

Optional是一个容器,里面放着我们的返回值(就是真正要用的东西),但是这个真正要用的东西可能是null呀,所以每次都要判断,但是Optional不可能是null。所以不要在判断是否为null了。


1
2
3
4
5
6

public void print(String s) {
    System.out.println(s);
}
 
String x = //...
Optional<String> opt = //...

上面代码中,x可能是null,opt不可能是null。

建立Optional对象

首先让我们来看一下如何构造Optional对象。Optional对象有一系列静态工厂方法创建出来。


1
2
3

opt = Optional.of(notNull);
opt = Optional.ofNullable(mayBeNull);
opt = Optional.empty();

以上3个方法,第一个of()从一个非null的对象创建Optional,如果传给他null,就有异常NullPointerException。
ofNullable()可以由任意对象(包括null)创建Optional。empty方法总是返回一个空的Optional。

ifPresent()方法:真的存在吗?

Optional在使用的时候,有一些主要的方法,首先是ifPresent。顾名思义,即使对象是否存在的意思。就是说里面装的东西是不是null,这个和判断对象是否为null基本上也是等价的。


1
2
3

if (x != null) {
    print(x);
}

以上代码等价于,下面的任何一句话。


1
2

opt.ifPresent(x -> print(x));
opt.ifPresent(this::print);

filter()方法:过滤一下吧

另外一个方法是filter()。就是可以做一些条件判断,判断Optional里面的东西,是否满足某一个条件,满足了,就可以做一些额外的操作。


1
2
3

if (x != null && x.contains("ab")) {
    print(x);
}

等价于:


1
2
3

opt.
   filter(x -> x.contains("ab")).
   ifPresent(this::print);

如果不喜欢这种函数式的风格,也可以这么写,更好懂:


1
2
3

if(opt.isPresent() && opt.get().contains("ab")) {
    print(opt.get());
}

上面的get()方法就是把Optional的东西拿出来了。

map()方法:执行一些操作

map()方法可以对值做一些处理,将一个操作作用于里面的东西。比如,字符串去掉首尾空格后打印出来:


1
2
3
4
5
6

if (x != null) {
    String t = x.trim();
    if (t.length() > 1) {
        print(t);
    }
}

等价于:


1
2
3
4

opt.
    map(String::trim).
    filter(t -> t.length() > 1).
    ifPresent(this::print);

map()方法会对Optional里的内容做处理,但如果Optional里的是null,他什么都不做(不会有异常哦),只返回empty。
map()还有一个特点是,它是类型安全的。


1
2

Optional<String>  opt = //...
Optional<Integer> len = opt.map(String::length);

一个String的Optional,取出他们的长度,变成了Integer。map()之后的数据类型就是Integer了。

orElse()/orElseGet() 需要一个默认值吗?

如果你在拿到Optional的数据后,发现是他null,你想做一些默认的返回值,执行类似下面的操作:


1

int len = (x != null)? x.length() : -1;

可以这么写:


1

int len = opt.map(String::length).orElse(-1);

如果默认值需要被动态计算出来,就是可以用orElseGet(),它接受一个Supplier:


1
2
3

int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

flatMap() 不会有Null的map

flatMap()方法和map()类似,不同点是,map可以返回任意类型,系统会自动包装为Optional,但是flatMap必须返回Optional,系统不会自动做包装。


1
2
3

//返回值是Optional
public Optional<String> tryFindSimilar(String s)  
Optional<String> similar =       opt.flatMap(this::tryFindSimilar);

orElseThrow() 如果值不存在,抛一个异常吧!

以下代码等价,一看便知:


1
2
3
4
5
6
7
8
9
10
11

public char firstChar(String s) {
    if (s != null && !s.isEmpty())
        return s.charAt(0);
    else
        throw new IllegalArgumentException();
}

opt.
    filter(s -> !s.isEmpty()).
    map(s -> s.charAt(0)).
    orElseThrow(IllegalArgumentException::new);

实例展示

假如有一个Person 对象,它有一个Address 属性,而Address属性还嵌套一个validFrom的日期。所有的值都可能是null。
好了,现在我们来判断一下Person 的这些属性是否是Valid的。


1
2
3
4
5
6
7
8
9
10

private boolean validAddress(NullPerson person) {
    if (person != null) {
        if (person.getAddress() != null) {
            final Instant validFrom = person.getAddress().getValidFrom();
            return validFrom != null && validFrom.isBefore(now());
        } else
            return false;
    } else
        return false;
}

或者也可以这么写:


1
2
3
4

return person != null &&
       person.getAddress() != null &&
       person.getAddress().getValidFrom() != null &&
       person.getAddress().getValidFrom().isBefore(now());

总之 都不是很好看。

但如果这些属性都是Optional,那么看起来会稍微舒服一点。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Person {
 
    private final Optional<Address> address;
 
    public Optional<Address> getAddress() {
        return address;
    }
 
    //...
}
 
class Address {
    private final Optional<Instant> validFrom;
 
    public Optional<Instant> getValidFrom() {
        return validFrom;
    }

//...
}

以下代码就实现了一样的判断:


1
2
3
4
5

return person.
        flatMap(Person::getAddress).
        flatMap(Address::getValidFrom).
        filter(x -> x.before(now())).
        isPresent();

使用Optional以后,NullPointerException 就从此消失了。

将一个Optional转为List或者Set

Optional是一个集合,虽然里面只有0或者1个元素,但它一样是一个集合。如果要转为List或者Set,一般的写法可以是:


1
2
3
4
5

public static <T> List<T> toList(Optional<T> option) {
    return option.
            map(Collections::singletonList).
            orElse(Collections.emptyList());
}

或者更传统的写法:


1
2
3
4
5
6

public static <T> List<T> toList(Optional<T> option) {
    if (option.isPresent())
        return Collections.singletonList(option.get());
    else
        return Collections.emptyList();
}

但是在java8里,其实只需要这么写:


1
2
3
4
5

import static java.util.stream.Collectors.*;
//转为List
List<String> list = collect(opt, toList());
//转为Set
Set<String>  set  = collect(opt, toSet());

本文代码内容来自于:http://www.nurkiewicz.com/2013/08/optional-in-java-8-cheat-sheet.html

时间: 2024-10-12 07:58:22

Java8 中的Optional类详解的相关文章

【Todo】Java学习笔记 100==100 &amp; Reflection API &amp; Optional类详解 &amp; DIP、IoC、DI &amp; token/cookie/session管理会话方式

为什么1000 == 1000返回为False,而100 == 100会返回为True?   Link Java Reflection API:Link Java8 Optional 类深度解析: Link 深入理解DIP.DI及IoC容器: Link 3种会话管理的方式: Link

Cocos2dx-3.x 中CCCamera相机类详解及源码分析

Cocos2d-x 3.3版本中加入了相机这个类,该类在3D游戏中是必不可少的,在3D立体游戏中,往往需要视野角度的变化,通过相机的变换才能观察和体验整个游戏世界. CCCamera类基本使用 在游戏中一般有两种类型的相机:一种是透视相机,它在3D游戏中十分常见:另一种是正交相机,它没有透视相机的近大远小的效果而是相机内任何位置的物体大小比例都是一样的. 上图是透视相机的原理图,一般来说,我们通过以下代码创建: _camera = Camera::createPerspective(60, (G

MFC中CString.Format类详解

在MFC程序中,使用CString来处理字符串是一个很不错的选择.CString既可以处理Unicode标准的字符串,也可以处理ANSI标准的字符串.CString的Format方法给我们进行字符串的转换带来了很大的方便,比如常见的int.float和double这些数字类型转换为CString字符串只需一行代码就可以实现. 先看看Format用于转换的格式字符: %c                 单个字符 %d                 十进制整数(int) %ld         

java 中的math类详解

math类包含完成基本数学函数所需的方法.这些方法基本可以分为三类:三角函数方法.指数函数方法和服务方法.在math类中定义了PI和E两个double型常量, PI就是π的值,而E即e指数底的值,分别是:3.141592653589793和2.718281828459045: 三角函数方法 math类包含下面的三角函数方法: Math.toDegrees这个方法是将-π/2到π/2之间的弧度值转化为度,例如:Math.toDegrees(Math.PI/2)结果为90.0: Math.toRad

hadoop中典型Writable类详解

本文地址:http://www.cnblogs.com/archimedes/p/hadoop-writable.html,转载请注明源地址. Hadoop将很多Writable类归入org.apache.hadoop.io包中,在这些类中,比较重要的有Java基本类.Text.Writable集合.ObjectWritable等,重点介绍Java基本类和ObjectWritable的实现. 1. Java基本类型的Writable封装 目前Java基本类型对应的Writable封装如下表所示.

Java8中的final关键字详解

final修饰类 当用final去修饰一个类的时候,表示这个类不能被继承. 将类定义成final后,结果只是禁止被继承.由于禁止了继承,所以一个final类中的所有方法都默认为final. 在自己设计一个类的时候,要想好这个类将来是否会被继承,如果可以被继承,则该类不能使用fianl修饰,在这里呢,一般来说工具类我们往往都会设计成为一个fianl类.在JDK中,String被设计为final类型. public final class Feige { public final void say(

Java中的Properties类详解

1.Properties类是什么? Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置.就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的. 2.常用的方法 getProperty(String key)   在此属性列表中搜索具有指定键的属性.如果在此属性列表中找不到该键,

mybatis中关于example类详解

这几天刚接触example,很多内容都是破碎的,写一篇文章加深理解. 一.什么是example类 mybatis-generator会为每个字段产生Criterion,为底层的mapper.xml创建动态sql.如果表的字段比较多,产生的example类会十分庞大.理论上通过example类可以构造你想到的任何筛选条件.在mybatis-generator中加以配置,配置数据表的生成操作就可以自动生成example了.具体配置可以参考 Mybatis-Generator的具体使用方法 - zor

Unity中常用Time类详解

只读:Time.time:表示从游戏开发到现在的时间,会随着游戏的暂停而停止计算.Time.deltaTime:表示从上一帧到当前帧的时间,以秒为单位.Time.unscaledDeltaTime:不考虑timescale时候与deltaTime相同,若timescale被设置,则无效.Time.timeSinceLevelLoad:表示从当前Scene开始到目前为止的时间,也会随着暂停操作而停止.Time.unscaledTime:不考虑timescale时候与time相同,若timescal