Java 8 知识归纳(二)—— Optional

      Optional<T>类是一个容器类,代表一个值存在不存在。Optional<T>用于避免和 null 检查相关的 bug

创建Optional

  • Optional.empty() —— 创建一个空的Optional对象

     Optional<String> optStr = Optional.empty();
    
  • Optional.of() —— 依据非空值创建一个Optional对象。如果试图传入一个null值,会马上抛出一个NullPointerException

     Optional<String> optStr = Optional.of(str);
    
  • Optional.ofNullable() —— 创建一个允许为null值的Optional对象。

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

mapflatMap

  • map操作 —— 当Optional的值不为空时,将Optional的值转换为对应的值,并将其封装成Optional对象返回。如果原本的Optional对象的值为空,则返回一个空的 Optional对象。

     public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
         Objects.requireNonNull(mapper);
         if (!isPresent()) {
             return empty();
         } else {
             //拿到新值后,再封装成Optional类型
             return Optional.ofNullable(mapper.apply(value));
         }
     }
    
  • flatMap 操作 —— 当Optional的值不为空时,将Optional的值转换为对应的值,新的值必须为Optional类型,并将其直接返回。如果原本的Optional对象的值为空,则返回一个空的Optional对象。

     public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
         Objects.requireNonNull(mapper);
         if (!isPresent()) {
             return empty();
         } else {
             @SuppressWarnings("unchecked")
             //拿到新值后强转Optional类型,不进行封装
             Optional<U> r = (Optional<U>) mapper.apply(value);
             return Objects.requireNonNull(r);
         }
     }
    

注:

OptionalmapflatMap如何选择?

      map会将转换好的值进行一次Optional包装;flatMap会确保转换好的值为Optional对象,然后直接返回。使用 map 还是 flatMap 取决于 转换好的值 是否是Optional对象

      如果 转换好的值 不是Optional对象,使用map,对其进行再次包装,以便执行进一步Optional的操作。

      如果 转换好的值Optional 对象,使用flatMap,直接返回。

Optional的其他行为

  • isPresent方法:Optional 包含值的时候返回 true ,否则返回 false
  • ifPresent方法:当值存在时,使用该值执行给定的代码块,否则什么都不做。
    • 与Kotlin的安全调用运算符相似,只有值不为空时,具体方法才会被调用。
  • get方法: 如果值存在则将其返回,否则抛出一个NoSuchElement Exception 异常。
  • orElse方法:如果值存在则将其返回,否则返回一个默认值
  • filter方法:如果值存在,并且满足提供的谓词,就返回自身;否则返回一个空的Optional对象。
  • orElseGet方法:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值。(Supplier方法只有在Optional不为空时才执行调用)
  • orElseThrow方法:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的异常。
  • orElseThrow重载方法(无参):如果有值则将其返回,否则直接抛出NoSuchElementException(Java 10)
  • or方法:如果值存在则将其返回,否则返回由指定的Supplier接口生成的另一个 Optional 对象。(Java 9)
  • ifPresentOrElse方法:如果值存在,则使用该值作为参数,执行指定的Consumer接口;如果该值不存在,则执行给定的Runnable,处理值为空的情况。(Java 9)

Optional与序列化

      对于值可能缺失(即可能为null)的属性,可以将其使用Optional包裹,明确表示该属性的值可缺失,类似kotlin的可空类型,强制需要进行空检查。

public class Person{
    //Car可能为null,使用Optional对其进行封装
    private Optional<Car> car;

    public Optional<Car> getCar() {return car; }
}

      但由于Optional的设计初衷仅仅是要支持能返回Optional对象的语法,因此它没实现 Serializable 接口。如果使用某些要求序列化的库或框架,在域模型中使用 Optional,有可能引发程序故障。

      如果一定要实现序列化的域模型,替代方案是:提供一个能访问声明为Optional、变量值可能缺失的接口:

public class Person{
    //虽然知道car可空,但不提倡一开始就定义Optional<Car>类型。
    private Car car;
    public Optional<Car> getCarAsOptional(){
        return Optional.ofNullable(car);
    }
}

Optional 与 流

       JAVA 9 引入了 Optionalstream() 方法,该方法可以把一个含值的 Optional 对象转换成由该值构成的Stream 对象,或者把一个空的 Optional 对象转换成等价的空Stream。该方法为处理由 Optional 构成的 Stream 提供极大的便利。

//Optional#stream源码
public Stream<T> stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}

借助Optional,可以安全的对流元素进行转换、刷选的操作:

//依据前面序列化的要求,提供返回Optional封装属性的方法。
class Person{
    private Car car;
    public Optional<Car> getCarAsOptional() { return Optional.ofNullable(car); }
}

class Car{
    private Insurance insurance;
    public Optional<Insurance> getInsuranceAsOptional() { return Optional.ofNullable(insurance); }
}

class Insurance{
    private String name;
    public String getName() { return name; }
}

//接收一个Person列表
public Set<String> getCarInsuranceNames(List<Person> persons){
    return persons.stream()
        //将流转换为Stream<Optional<Car>>
        .map(Person::getCarAsOptional)
        //将流转换为Stream<Optional<Insurance>>
        .map(optCar -> optCar.flatMap(Car::getInsuranceAsOptional))
        //将流转换为Stream<Optional<String>>
        .map(optIns -> optIns.map(Insurance::getName))
        //该流转换成Stream<String>
        //如果使用Java8的,也可以参考Optional#stream进行处理
        .flatMap(Optional::stream)
        //将结果收集成Set
        .collect(Collectors.toSet());
}

       在 JDK 1.8 的环境下,可模仿Optional#stream()写一个将Optional对象转换为Stream对象的静态方法,然后使用方法引用将替代Optional::stream即可。这里可以使用OptionalUtility::stream 替代 Optional::stream

//OptionalUtility.java
public static <T> Stream<T> stream(Optional<T> optional) {
    if (!optional.isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(optional.get());
    }
}

      对于早已定义好,不提供一个能访问声明为Optional、变量值可能缺失的接口的域模型,可以手动将流的元素转换为Optional对象:

class Person{
    private Car car;
    public Car getCar() {return car;}
}

 class Car{
    private Insurance insurance;
    public Insurance getInsurance() { return insurance;}
 }

class Insurance{
    private String name;
    public String getName() { return name; }
}
persons.stream()
    //对元素使用Optional包装
    .map(Optional::ofNullable)
    .map(optPer -> optPer.map(Person::getCar))
    .map(optCar -> optCar.map(Car::getInsurance))
    .map(optIns -> optIns.map(Insurance::getName))
    //如果使用Java8的,也可以参考Optional#stream进行处理
    //拆除Optional包装
    .flatMap(Optional::stream)
    .collect(Collectors.toSet());

异常与Optional

      由于某些原因,函数无法返回某个值,除了返回null外,Java API 比较常见的替代做法是抛出一个异常(最典型的例子就是Interger.parseInt() )。我们可以使用空的 Optional 对象,对遭遇无法转换的String进行建模时返回的非法值进行建模,从而不需要再封装try/catch :

//OptionalUtility.java
public static Optional<Integer> stringToInt(String s){
    try{
        return Optional.ofNullable(Integer.parseInt(s));
    }catch (Exception e){
        return Optional.empty();
    }
}

//
HashMap<String,String> map = new HashMap<>();
map.put("Java","8");
int version = Optional.ofNullable(map.get("Java"))
    //使用OptionalUtility#stringToInt进行转换。
    .flatMap(OptionalUtility::stringToInt)
    .orElse(0);

以不解包的方式组合两个Optional对象

      当需要操作两个Optional对象进行运算并返回包含结果的Optional对象时,或许你会想到以下实现方法:

public Insurance findInsurance(Person person,Car car){
    //经过运算得到正确的Insurance
    //此处模拟返回一个Insurance对象
    return insurance;
}

public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
    //判断两个Optional的值都存在
    if (person.isPresent() && car.isPresent()){
        //只有两个Optional的值都存在,才取出进行运算。
        return Optional.ofNullable(findInsurance(person.get(),car.get()));
    }else {
        //否则返回一个空Optional对象。
        return Optional.empty();
    }
}

      但这样的代码跟我们手动判空区别不大,但其实可以结合 flatMapmap,不解包的方式下实现:

public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
    //如果person的值不存在,则直接返回一个空Optional对象。
    //至于为什么用flatMap,因为car.map返回的是一个Optional对象
    return person.flatMap(p ->
        //如果map的值不存在,则直接返回一个空Optional对象。
        car.map(c ->
            //执行到这一步,说明两个Optional的值都存在,利用这两个值进行运算。
            //运算出的Insurance值,map函数会对其使用Optional进行封装。
            findInsurance(p,c))
    );
}

简化if-else

      当需要对某一个变量深度获取值时,往往会伴随多次判空,使用 Optional 能优化 if-else 结构:

public String getInsuranceByPerson(Person person){
    return Optional.ofNullable(person)
            .map(Person::getCar)
            .map(Car::getInsurance)
            .map(Insurance::getName)
            .orElse("daqi");
}

总结

       Optional类有时候并没有让代码变得更简洁,他的作用更多的是把 类型 转换为 对应的"可空类型" ,强制进行空检查(与Kotlin定义可空类型相似)。

       总体来说,Optional可确保 流 进行mapfilter操作时的空安全,以及对某个变量进行深度取值时简化if-else流程和确保空安全。当然也可以把普通的空检查转换为 Optional 后,再进行操作。

参考资料

Java实战(第2版)

Java8系列

Java 8 知识归纳(一)—— 流 与 Lambda
Java 8 知识归纳(二)—— Optional
Java 8 知识归纳(三)—— 日期API

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

推荐阅读更多精彩内容