2019独角兽企业重金招聘Python工程师标准>>>

Optional可以让你的代码具有可读性,且会避免出现空指针异常。

都说没有遇到过空指针异常的程序员不是Java程序员,null确实引发过很多问题。Java 8中引入了一个叫做java.util.Optional的新类可以避免null引起的诸多问题。

我们看一下null引用能导致哪些危害。首先创建一个类Computer,结构如下图所示:

当我们调用如下代码会怎样?

String version = computer.getSoundcard().getUSB().getVersion();

上述代码看似是没有问题的,但是很多计算机(比如,树莓派)其实是没有声卡的,那么调用getSoundcard()方法可定会抛出空指针异常了。

一个常规的但是不好的的方法是返回一个null引用来表示计算机没有声卡,但是这就意味着会对一个空引调用getUSB()方法,显然会在程序运行过程中抛出控制异常,从而导致程序停止运行。想想一下,当你的程序在客户端电脑上运行时,突然出现这种错是多尴尬的一件事? 伟大计算机科学Tony Hoare曾经写到:"我认为null引用从1965年被创造出来导致了十亿美元的损失。当初使用null引用对我最大的诱惑就是它实现起来方便。"

那么该怎么避免在程序运行时会出现空指针异常呢?你需要保持警惕,并且不断检查可能出现空指针的情况,就像下面这样:

String version = "UNKNOWN";
if(computer != null){Soundcard soundcard = computer.getSoundcard();if(soundcard != null){USB usb = soundcard.getUSB();if(usb != null){version = usb.getVersion();}}}

然而,你可以看到上述代码有太多的null检查,整个代码结构变得非常丑陋。但是我们又不得不通过这样的判断来确保系统运行时不会出现空指针。如果在我们的业务代码中出现大量的这种空引用判断简直让人恼火,也导致我们代码的可读性会很差。

如果你忘记检查要给值是否为空,null引用也是存在很大的潜在问题。这篇文章我将证明使用null引用作为值不存在的表示是不好的方法。我们需要一个更好的表示值不存在的模型,而不是再使用null引用。

Java 8引入了一个新类叫做java.util.Optional<T>,这个类的设计的灵感来源于Haskell语言和Scala语言。这个类可以包含了一个任意值,像下面图和代码表示的那样。你可以把Optional看做是一个有可能包含了值的值,如果Optional不包含值那么它就是空的,下图那样。

public class Computer {private Optional<Soundcard> soundcard;public Optional<Soundcard> getSoundcard() { ... }...
}public class Soundcard {private Optional<USB> usb;public Optional<USB> getUSB() { ... }}public class USB{public String getVersion(){ ... }
}

上述代码展现了一台计算机有可能包换一个声卡(声卡是有可能存在也有可能不存在)。声卡也是有可能包含一个USB端口的。这是一种改善方法,该模型可以更加清晰的反映一个被给定的值是可以不存在的。

但是该怎么处理Optional<Soundcard>这个对象呢?毕竟,你想要获取的是USB的端口号。很简单,Optional类包含了一些方法来处理值是否存在的状况。和null引用相比Optional类迫使你在你要做值是否相关处理,从而避免了空指针异常。

需要说明的是Optional类并不是要取代null引用。相反地,是为了让设计的API更容易被理解,当你看到一个函数的签名时,你就可以判断要传递给这个函数的值是不是有可能不存在。这就促使你要打开Optional类来处理确实值的状况了。

采用Optional模式

啰嗦了这么多,来看一些代码吧!我们先看一下怎么使用Optional改写传统的null引用检测后是什么样子。在这边文章的末尾你将会明白怎么使用Optional。

String name = computer.flatMap(Computer::getSoundcard).flatMap(Soundcard::getUSB).map(USB::getVersion).orElse("UNKNOWN");

创建Optional对象

可以创建一个空的Optional对象:

Optional<Soundcard> sc = Optional.empty();

接下来是创建一个包含非null值的Optional:

SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard);

如果声卡null,空指针异常会立即被抛出(这比在获取声卡属性时才抛出要好)。

通过使用ofNullable,你可以创建一个可能包含null引用的Optional对象:

Optional<Soundcard> sc = Optional.ofNullable(soundcard);

如果声卡是null 引用,Optional对象就是一个空的。

对Optional中的值的处理

既然现在已经有了Optional对象,你可以调用相应的方法来处理Optional对象中的值是否存在。和进行null检测相比,我们可以使用ifPresent()方法,像下面这样:

Optional<Soundcard> soundcard = ...;
soundcard.ifPresent(System.out::println);

这样就不必再做null检测,如果Optional对象是空的,那么什么信息将不会打印出来。

你也可以使用isPresent()方法查看Optional对象是否真的存在。另外,还有一个get()方法可以返回Optional对象中的包含的值,如果存在的话。否则会抛出一个NoSuchElementException异常。这两个方式可以像下面这样搭配起来使用,从而避免异常:

if(soundcard.isPresent()){System.out.println(soundcard.get());
}

但是这种方式不推荐使用(它和null检测相比没有什么改进),下面我们将会探讨一下工作惯用的方式。

返回默认值和相关操作

当遇到null时一个常规的操作就是返回一个默认值,你可以使用三元表达式来实现:

Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");

使用Optional对象的话,你可以orElse()使用重写,当Optional是空的时候orElse()可以返回一个默认值:

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut"));

类似地,当Optional为空的时候也可以使用orElseThrow()抛出异常:

Soundcard soundcard = maybeSoundCard.orElseThrow(IllegalStateException::new);

使用filter过滤特定的值

我们常常会调用一个对象的方法来判断它的一下属性。比如,你可能需要检测USB端口号是否是某个特定值。为了安全起见,你需要检查指向USB的医用是否是null,然后再调用getVersion()方法,像下面这样:

USB usb = ...;
if(usb != null && "3.0".equals(usb.getVersion())){System.out.println("ok");
}

如果使用Optional的话可以使用filter函数重写:

Optional<USB> maybeUSB = ...;
maybeUSB.filter(usb -> "3.0".equals(usb.getVersion()).ifPresent(() -> System.out.println("ok"));

filter方法需要一个predicate对向作为参数。如果Optional中的值存在并且满足predicate,那么filter函数将会返回满足条件的值;否则,会返回一个空的Optional对象。

使用map方法进行数据的提取和转化

一个常见的模式是提取一个对象的一些属性。比如,对于一个Soundcard对象,你可能需要获取它的USB对象,然后判断它的的版本号。通常我们的实现方式是这样的:

if(soundcard != null){USB usb = soundcard.getUSB();if(usb != null && "3.0".equals(usb.getVersion()){System.out.println("ok");}
}

我们可以使用map方法重写这种检测null,然后再提取对象类型的对象。

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);

这个和使用stream的map函数式一样的。使用stream需要给map函数传递一个函数作为参数,这个传递进来的函数将会应用于stream中的每个元素。当stream时空的时候,什么也不会发生。

Optional中包含的值将会被传递进来的函数转化(这里是一个从声卡中获取USB的函数)。如果Optional对象时空的,那么什么也不会发生。

然后,我们结合map方法和filter方法过滤掉USB的版本号不是3.0的声卡。

maybeSoundcard.map(Soundcard::getUSB).filter(usb -> "3.0".equals(usb.getVersion()).ifPresent(() -> System.out.println("ok"));

这样我们的代码开始变得像有点像开始我们给出的样子,没有了null检测。

使用flatMap函数传递Optional对象

现在已经介绍了一个可以使用Optional重构代码的例子,那么我们应该如何使用安全的方式实现下面代码呢?

String version = computer.getSoundcard().getUSB().getVersion();

注意上面的代码都是从一个对象中提取另一个对象,使用map函数可以实现。在前面的文章中我们设置了Computer中包含的是一个Optional<Soundcard>对象,Soundcard包含的是一个Optional<USB>对象,因此我们可以这么重构代码

String version = computer.map(Computer::getSoundcard).map(Soundcard::getUSB).map(USB::getVersion).orElse("UNKNOWN");

不幸的是,上面的代码会编译错误,那么为什么呢?computer变量是Optional<Computer>类型的,所以它调用map函数是没有问题的。但是getSoundcard()方法返回的是一个Optional<Soundcard>的对象,返回的是Optional<Optional<Soundcard>>类型的对象,进行了第二次map函数的调用,结果调用getUSB()函数就变成非法的了。下面的图描述了这种场景:

map函数的源码实现是这样的:

 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {Objects.requireNonNull(mapper);if (!isPresent())return empty();else {return Optional.ofNullable(mapper.apply(value));}}

可以看出map函数还会再调用一次Optional.ofNullable(),从而导致返回Optional<Optional<Soundcard>>

Optional提供了flatMap这个函数,它的设计意图是当对Optional对象的值进行转化(就像map操作)然后一个两级Optional压缩成一个。下面的图展示了Optional对象通过调用map和flatmap进行类型转化的不同:

因此我们可以这样写:

String version = computer.flatMap(Computer::getSoundcard).flatMap(Soundcard::getUSB).map(USB::getVersion).orElse("UNKNOWN");

第一个flatMap保证了返回的是Optional<Soundcard>而不是Optional<Optional<Soundcard>>,第二个flatMap实现了同样的功能从而返回的是 Optional<USB>。注意第三次调用了map(),因为getVersion()返回的是一个String对象而不是一个Optional对象。

我们终于把刚开始使用的嵌套null检查的丑陋代码改写了可读性高的代码,也避免了空指针异常的出现的代码。

总结

在这片文章中我们采用了Java 8提供的新类java.util.Optional<T>。这个类的初衷不是要取代null引用,而是帮助设计者设计出更好的API,只要读到函数的签名就可知道该函数是否接受一个可能存在也可能不存在的值。另外,Optional迫使你去打开Optional,然后处理值是否存在,这就使得你的代码避免了潜在的空指针异常。

最后

感谢阅读,有兴趣可以关注微信公众账号获取最新推送文章。

转载于:https://my.oschina.net/liuyatao19921025/blog/1605614

使用Java 8 Optional避免空指针异常相关推荐

  1. java 8 Optional解决空指针异常问题

    在写程序时,经常需要对创建的对象或属性进行null值判断,但是有时可能会疏忽没有对null进行判断,就会引发空指针问题,null值在程序设计语言中,是为了表示变量值的缺失: java8中引入了Opti ...

  2. Java 8 - Optional全解

    文章目录 在Optional出现之前经常遇到的空指针异常 采用防御式减少NullPointerException (深度质疑) null-安全的第二种尝试(过多的退出语句) Optional的介绍以及 ...

  3. Java 8 Optional 类

    Java 8 Optional 类  Java 8 新特性 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. ...

  4. java 8 optional 类,Java8新特性-Optional类

    Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念.并且可以避免空指 ...

  5. 【Java】Optional容器

    说明 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. Optional 是个容器:它可以保存类型T的值,或者 ...

  6. Java 8 Optional 类 学习

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010046908/article/details/62216945 1. Optional类的简介 ...

  7. java.util.Optional

    API文档:https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html https://www.runoob.com/java/ ...

  8. Java 8 Optional:优雅地避免 NPE

    本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查.在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的. 空指针异常(NullPo ...

  9. JAVA高级——Optional

    JAVA高级--Optional 二.为何要避免null指针 三.使用Optional优化null判断 3.1.1 使用Optional优化Car类 3.1.2 Optional的几种模式 3.1.3 ...

最新文章

  1. Android-----View绘制流程以及invalidate()等相关方法分析 .
  2. yolov3 onnx nms
  3. 口语学习Day7:今天聊聊美国超市的物价
  4. [转]Windows的批处理脚本
  5. keepalived实现nginx高可用主备集群配置过程
  6. mongodb mysql 写_MongoDB与MySQL关于写确认的异同
  7. 转 ABAP_ALV_Function方式与OO方式(较为简单、普通的ALV)
  8. mysql分库分表面试_【53期】面试官:谈一下数据库分库分表之后,你是如何解决事务问题?...
  9. BG-UI,一个可以快速上手的后台UI框架
  10. 定时器(setTimeout/setInterval)调用带参函数失效解决方法
  11. Linux计划任务入门详解
  12. QFIL and FASTBOOT
  13. Android中经纬度与度分秒互转
  14. 基于C#和三菱PLC通过MX Component进行通信的具体方法
  15. 常用的即时通讯软件有哪些
  16. 景深与光圈、拍摄距离和镜头焦距的关系
  17. TinyKv Project1 Standalone KV
  18. 介绍一种冷门但简单的双拼方案——紫光双拼
  19. 电源设计经验谈1-9
  20. html页面怎样打印二分之一,打印二分之一a4纸 大小的纸张 Word该如何设置?

热门文章

  1. String indexOf 算法
  2. PostgreSQL数据库dblink和postgres_fdw扩展使用比较
  3. 寒冰linux视频教程笔记8 系统监控
  4. 从PRISM开始学WPF(四)Prism-Module?
  5. NoSQL介绍(三)
  6. Java知多少(42)泛型通配符和类型参数的范围
  7. HTML标签大全(三)
  8. SQLServer Date
  9. TP5实现支付宝电脑网站支付学习笔记
  10. ZOJ 2747 Paint the Wall(离散化+暴力)题解