Java8新特性之空指针异常的克星Optional类

简介: Java8新特性之空指针异常的克星Optional类

image.pngJava8新特性系列我们已经介绍了Stream、Lambda表达式、DateTime日期时间处理,最后以“NullPointerException” 的克星Optional类的讲解来收尾。

背景

作为开发人员每天与NullPointerException斗智斗勇。每接收到参数或调用方法获得值得判断一下是否为null。稍不留意,空指针异常就像幽灵一样出现了。

这篇文章我们来学习Java8是如何通过Optional类来避免空指针异常的。

先来看一下不使用Optional类时,我们为了防止NullPointerException会怎么处理。












public String getParentName(Person son) {  if (son != null) {    Person parent = son.getParent();    if (parent != null) {      return parent.getUsername();    } else {      return "--";    }  }  return "--";}

为了防止出现异常,需要不停的判断对象是否为null。但如果业务逻辑比较复杂,会涌现出大量的ifelse。看似逻辑缜密,但易读性却并不高。

为了解决相关问题,在Effective Java中建议若方法返回类型为集合,则通过返回空集合以避免 NullPointerException,真是煞费苦心。

先看一下上面的代码使用Optional之后会变成什么样子。




public String getParentNameWithOptional(Person son) {  return Optional.ofNullable(son).map(Person::getParent).map(Person::getUsername).orElse("--");}

对照一下代码,看看神不神奇?

Optional类简介

java.util.Optional<T>类的引入很好的解决空指针异常,类声明如下:


public final class Optional<T> {}

java.util.Optional<T>类是一个封装了Optional值的容器对象,Optional值可以为null,如果值存在,调用isPresent()方法返回true,调用get()方法可以获取值。

通过源代码会发现,它并没有实现java.io.Serializable接口,因此应避免在类属性中使用,防止意想不到的问题。

除了Optional类之外,还扩展了一些常用类型的Optional对象,比如:OptionalDouble、OptionalInt、OptionalLong。用法基本上相似。

下面通过具体的操作和功能来了解Optional类。

创建Optional对象

创建Optional对象有三种方法:empty()、of()、ofNullable(),均为静态方法。

如果Optional对象没有值则用empty()方法。


Optional empty = Optional.empty();

如果确定Optional对象的值不为null,则可用of()方法。


Optional stringOptional = Optional.of("Hello 公众号:程序新视界");

如果不确定Optional对象的值是否为null,则可用ofNullable()。比如上面,不确定Person对象是不否null,就用了ofNullable()方法。当然,也可以直接给该方法传null。


Optional ofNullOptional = Optional.ofNullable(null);

此时,通过调用其isPresent方法可以查看该Optional中是否值为null。



boolean bool = ofNullOptional.isPresent();System.out.println(bool);

此时如果直接调用get方法获取值,则会抛出异常。

ofNullOptional.get();

get获取Optional中的值

通过get方法可获取Optional中的值,但如果值为null,则会抛出异常。



Optional ofNullOptional = Optional.ofNullable(null);ofNullOptional.get();

异常信息:




java.util.NoSuchElementException: No value present    at java.util.Optional.get(Optional.java:135)...

此时,需要另外一个方法的辅助:isPresent()。该方法可判定Optional中是否有值,如果有则返回true,如果没有则返回false。






Optional ofNullOptional = Optional.ofNullable(null);boolean bool = ofNullOptional.isPresent();if(bool){  ofNullOptional.get();}

map获取Optional中的值

对于对象操作,也可以通过map来获取值,最开始简化的例子就是如此。



Optional<Person> sonOptional = Optional.ofNullable(son);System.out.println(sonOptional.map(Person::getUsername));

map方法,如果有值,则对其执行调用映射函数得到返回值。如果返回值不为null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。

flatMap获取Optional中的值

如果有值,则返回Optional类型返回值,否则返回空Optional。flatMap与map方法类似。但flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。



Optional<Person> sonOptional = Optional.ofNullable(son);sonOptional.flatMap(OptionalTest::getOptionalPerson);

调用的是当前类OptionalTest的另外一个方法:




public static Optional<Person> getOptionalPerson(Person person){  return Optional.ofNullable(person);}

orElse获取Optional中的值

orElse方法,如果有值就返回,否则返回一个给定的值作为默认值;


Optional.empty().orElse("--");

上面这种情况就会返回“--”。

在此,这种操作与三目运算效果一样。


str != null ? str : "--"

orElseGet获取Optional中的值

orElseGet()方法与orElse()方法作用类似,但生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;






Optional.empty().orElseGet(() -> {      String a = "关注";      String b = "公众号:程序新视界";      return a + b;    });

很显然,这里可以处理更多的业务逻辑。

orElseThrow获取Optional中的值

orElseThrow()方法与get()方法类似,当值为null时调用会抛出NullPointerException异常,但该方法可以指定抛出的异常类型。


Optional.empty().orElseThrow(()-> new RuntimeException("请先关注公众号!"));

此时打印异常信息为:


Optional.empty().orElseThrow(()-> new RuntimeException("请先关注公众号!"));

判断并执行操作

ifPresent方法,可对值进行判断然后打印,接收参数为Consumer<? super T>函数式接口。


Optional.of("公众号:程序新视界").ifPresent(System.out::println);

当然,也可以在函数中执行其他复杂操作:




Optional.of("公众号:程序新视界").ifPresent((val)->{      System.out.println("欢迎关注" + val);    });

filter()方法过滤

filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤:






Optional.of("公众号:程序新视界").filter((val)->{  return val.contains("程序新视界");});// 简化写法Optional.of("公众号:程序新视界").filter((val)-> val.contains("程序新视界"));

使用误区

关于使用Optional的误区有以下:

  • 正确的使用创建方法,不确定是否为null时尽量选择ofNullable方法。
  • 避免用在成员变量上(原因上面已经提到);
  • 避免直接调用Optional对象的get和isPresent方法;


最后一条可能难理解,试想一下如果先用isPresent方法获得是否存在,然后决定是否调用get方法和之前的ifelse判断并无二致。

Java8提倡函数式编程,新增的许多API都可以用函数式编程表示,Optional类也是其中之一。具体写法可参看文章最初的示例。

目录
相关文章
|
10月前
|
Java 程序员 数据库连接
我们详细地讲解一下 Java 异常及要如何处理
我是小假 期待与你的下一次相遇 ~
225 1
|
SQL druid Oracle
【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常
客户Java日志中出现异常,影响Druid的merge SQL功能(将SQL字面量替换为绑定变量以统计性能),但不影响正常业务流程。原因是Druid在merge SQL时传入null作为dbType,导致无法解析递归查询中的`start`关键字。
|
SQL Java 中间件
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
在BeetISQL 2.13.8版本中,客户使用batch insert向yashandb表插入数据并尝试获取自动生成的sequence id时,出现类型转换异常。原因是beetlsql在prepareStatement时未指定返回列,导致yashan JDBC驱动返回rowid(字符串),与Java Bean中的数字类型tid不匹配。此问题影响业务流程,使无法正确获取sequence id。解决方法包括:1) 在batchInsert时不返回自动生成的sequence id;2) 升级至BeetISQL 3,其已修正该问题。
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
779 14
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
4258 5
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
633 13
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
497 1
|
机器人 Java
深度解析 Java 的 Optional 类(下)
深度解析 Java 的 Optional 类(下)
226 0
深度解析 Java 的 Optional 类(下)
|
Java
深度解析 Java 的 Optional 类(上)
深度解析 Java 的 Optional 类(上)
247 0
深度解析 Java 的 Optional 类(上)
|
6月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
328 1