厌倦了空指针异常?那么试试Java SE 8中的Optioanl吧

Java8引入Optional类来缓解空指针异常问题,通过示例展示了如何使用Optional改进代码,使其更易读并减少NullPointerException。Optional强迫开发者显式处理可能的空值,提高API设计的明确性。文章还提及其他语言如Groovy、Haskell和Scala处理null的方法。
摘要由CSDN通过智能技术生成

原文地址:https://www.oracle.com/technical-resources/articles/java/java8-optional.html

1、让你的代码更加易读,并且避免空指针异常

一个聪明的人曾经说过如果你没有处理空指针异常,那么你就不是一个真正的Java程序员。呵呵,开个玩笑,言归正传,空指针是许多问题的根源,因为它会导致值的缺失。Java8引入了新类,java.util.Optional可以缓解这些问题。
举个例子演示下null的危害。如下图,计算机类Computer,
图1

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的问题的。

2、如何取代NULL?

Groovy使用?避免空指针异常(很块,c#也使用了此方法,Java SE 7曾经提议增加此方法,但是最终发布时没有实现)。如下所示:
String version = computer?.getSoundcard()?.getUSB()?.getVersion();
这样就避免了检查是否是空指针的嵌套代码。
此外,Groovy还使用“?:”提供一个默认值。如下所示:
String version =
computer?.getSoundcard()?.getUSB()?.getVersion() ?: “UNKNOWN”;
其它的函数式编程语言,例如Haskell和Scala。Haskell包含了一个Maybe类型,其实就是包装了一个optional值。Maybe可以包含一个指定类型的值或者什么也不包含。没有空指针引用的概念。Scala有个类似的方法Option[T],封装了一个类型T的值或者空值。
那么Java SE 8是如何做的呢?

3、Optional 在外壳中

Java SE 8 新增了java.util.Optinal类,参考了Haskell和Scala。它封装了一个optional值,这个optional可能有值也可能是空值。如下图:
图2
可以向下面这样修改之前的代码:

public class Computer {
  private Optional<Soundcard> soundcard;  
  public Optional<Soundcard> getSoundcard() 
  { return soundcard; }
}

public class Soundcard {
  private Optional<USB> usb;
  public Optional<USB> getUSB() 
  { return usb; }

}

public class USB{
  public String getVersion(){ return ""; }
}

以上代码表示一个电脑可能有声卡,也可能没有声卡。这样的代码更加清晰的表示了一个电脑的实际情况。
但是怎么使用Optional对象呢?毕竟,你只是想要获取USB端口的版本号。Optional类包含了一些方法可以显示的处理有或者没有声卡的情况。但是,Optional最大的好处时,它会迫使你写代码是,想想这个电脑是否有声卡,这样你就可以避免空指针异常了。
Optional类的目的不是去替换每一个空指针,它让你设计的API方法,一目了然的表示是否会有空值,这样会让使用API的用户主动的去处理空指针异常,而不会忘了处理空指针导致运行时程序异常退出。

4、使用Optional方式

讨论了这么多,让我们写写代码吧。首先看下上面经典的null检查模板代码如何使用Optional替换。文章的最后,你就能理解如何使用Optional了。

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

5、创建Optional有几种创建Optional的方式,创建空对象:

Optional sc = Optional.empty();
创建非null对象的optional:
SoundCard soundcard = new Soundcard();
Optional sc = Optional.of(soundcard);
如果sounccard对象为null,则第二行代码执行时立即抛出空指针异常,而不是在实际访问soundcard对象的属性时。
Optional sc = Optional.ofNullable(soundcard);
如果soundcard对象为null,则sc是空对象。

6、如果值存在,则执行某个方法

如果不用Optional,则这样写代码:
SoundCard soundcard = …;
if(soundcard != null){
System.out.println(soundcard);
}
使用Optional后,这样写:
Optional soundcard = …;
soundcard.ifPresent(System.out::println);
你不用再检查null了,如果Optional对象的值为空,则不执行参数中的方法。
你也可以使用isPreset()方法去检查Optional对象是否有值。另外,使用get()方法可以获取Optional包含的值,如果它有值,否则,抛出NoSuchElementException异常。这两个方法可以联合使用:
if(soundcard.isPresent()){
System.out.println(soundcard.get());
}
但是,不推荐这样使用,这样和null检查没多大区别。

7、默认值和行为

如果值为null则返回一个默认值,否则返回值,通常情况下,这样写代码:
Soundcard soundcard =
maybeSoundcard != null ? maybeSoundcard
: new Soundcard(“basic_sound_card”);
使用Optional后,这样写代码:
Soundcard soundcard =maybeSoundcard.orElse(new Soundcard(“default”));
也可以使用orElseThrow()方法:
Soundcard soundcard =
maybeSoundCard.orElseThrow(IllegalStateException::new);

8、使用filter方法

你经常会调用一个方法,然后检查对象的某些属性。例如,你可能要检查USB端口是否是一个指定的版本。你以前可能这样写代码:
USB usb = …;
if(usb != null && “3.0”.equals(usb.getVersion())){
System.out.println(“ok”);
}
现在你可以这样写:
Optional maybeUSB = …;
maybeUSB.filter(usb -> “3.0”.equals(usb.getVersion())
.ifPresent(() -> System.out.println(“ok”));
filter方法使用一个预言函数作为参数。如果Optinal对象有匹配的值,则返回那个对象,否则,返回空的Optional对象。你肯能已经在Stream接口中见到了类似的使用方式。

9、使用map方法抽取和转换对象

另一个经常用的模式是从一个对象中抽取信息转换成另一个对象。
例如,从Soundcard对象中获取USB对象,然后检查usb的版本后是否正确。你可能会这样写代码:
if(soundcard != null){
USB usb = soundcard.getUSB();
if(usb != null && “3.0”.equals(usb.getVersion()){
System.out.println(“ok”);
}
}
现在你可以这样写,检查是否为null然后从soundcard对象中抽取:
Optional usb = maybeSoundcard.map(Soundcard::getUSB);
最终,我们可以联合起来使用map和filter方法。
maybeSoundcard.map(Soundcard::getUSB)
.filter(usb -> “3.0”.equals(usb.getVersion())
.ifPresent(() -> System.out.println(“ok”));
太棒了,我们的代码开始接近问题陈述式代码了,终于拜托了烦人的null检查了。

10、使用flatMap方法解决级联Optional对象

前面我们已经看到了一些使用Optional解决问题的模式了。那么下面的代码怎么办呢?
String version = computer.getSoundCard().getUSB().getVersion();
注意以上代码是从一个对象中抽取另一个对象,就像map方法那样。在文章的前面,我们修改了类Computer和Soundcard,使它们都包含了Optional对象,所以看起来我们可以像下面这样写:
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse(“UNKNOWN”);
很不幸,这段代码编译失败了。为什么呢?变量computer是Optional类型的对象,所以调用map方法是正确的。但是,getSoundcard方法返回了一个Optional类型的对象,这样就意味着map方法的结果是Optional<Optional>,所以,getUSB方法不可用。如下图:
在这里插入图片描述
那么我们如何解决这个问题呢?再一次,我们回忆下我们在streams中使用过的flatMap方法。所以我们的代码可以这样写:
String version = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse(“UNKNOWN”);
哇哦,从痛苦的嵌套null检查的代码到申明式代码(组合式、更加可读、避免空指针异常),我们走了过来。

11、总结

在这篇文章中,我们学会了如何使用java8中的新类:java.util.Optional。Optional的目的不是替换每一个单独的null引用检查,而是帮助你设计更好的API,让使用你的API的用户只看方法签名,就会知道是否是一个可选参数。另外,Optional迫使你主动打开Optional对象去处理空值,避免了空指针异常。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值