【JDK 8特性】 优雅的Optional空指针处理

乐猿社区,程序员的花果山java


那些年那NullPointerException

作为一个javaer的老司机,能够回忆一下,在咱的码字生涯中到底遇到过多少次java.lang.NullPointerException异常?NullPointerException做为一个RuntimeException级别的异常不用显示捕获,若不当心处理咱们常常会在生产日志中看到各类由NullPointerException引发的异常堆栈输出。程序员

咱们看看下面这个代码,用很传统很标准的Java编码风格编写:编程

String result = doSomething();
String [] str = result.split(",");

这一段代码很简单,平常的业务代码确定比这个复杂的多,而实际上咱们大量的Java编码都是按这种套路编写的,可是若是doSomething返回的是一个null,那么能够看出最终确定会抛出NullPointerException。在咱们编写业务代码时,不多会想到要处理这个可能会出现的null,直到咱们到了某个测试阶段,忽然蹦出一个NullPointerException异常,咱们才意识到原来咱们得像下面这样加一个判断来搞定这个可能会返回的null值。安全

String result = doSomething();
if(result!=null){
	String [] str = result.split(",");
}

利用Optional实现Java函数式编程

眼看像Scala等基于jvm的语言都有相似的语法糖了,java做为一统江湖的老大哥,怎么可能会没有自已的必杀技呢?新版本的Java,好比Java 8引入了一个新的Optional类。Optional类的Javadoc描述以下:app

它是一个容器,装载着非NULL元素(或者没有装载元素),提供了一系列的方法供咱们判断该容器里的对象是否存在(以及后续的操做)。less

对于上段代码,采用正确姿式Optional后变身为:jvm

String result = doSomething();
String[] str = Optional.ofNullable(result).map(str->str.split(",")).orElse(new String []{});

Optional经常使用必杀持

of

为非null的值建立一个Optional。编程语言

of方法经过工厂方法建立Optional类。须要注意的是,建立对象时传入的参数不能为null。若是传入参数为null,则抛出NullPointerException函数式编程

/调用工厂方法建立Optional实例
Optional<String> name = Optional.of("乐猿社区");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);

ofNullable

为指定的值建立一个Optional,若是指定的值为null,则返回一个空的Optional。函数

ofNullable与of方法类似,惟一的区别是能够接受参数为null的状况。示例以下:

//下面建立了一个不包含任何值的Optional实例
//例如,值为'null'
Optional empty = Optional.ofNullable(null);

isPresent

若是值存在返回true,不然返回false

//isPresent方法用来检查Optional实例中是否包含值
if (name.isPresent()) {
  //在Optional实例内调用get()返回已存在的值
  System.out.println(name.get());//输出乐猿社区
}

get

若是Optional有值则将其返回,不然抛出NoSuchElementException

try {
  Optional empty = Optional.ofNullable(null);
  //在空的Optional实例上调用get(),抛出NoSuchElementException
  System.out.println(empty.get());
} catch (NoSuchElementException ex) {
  System.out.println(ex.getMessage());//输出:No value present 
}

ifPresent

若是Optional实例有值则为其调用consumer,不然不作处理

要理解ifPresent方法,首先须要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接经过lambda表达式传入参数。

若是Optional实例有值,调用ifPresent()能够接受接口段或lambda表达式。相似下面的代码:

//ifPresent方法接受lambda表达式做为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
  System.out.println("The length of the value is: " + value.length());
});

orElse

若是有值则将其返回,不然返回指定的其它值。

若是Optional实例有值则将其返回,不然返回orElse方法传入的参数。示例以下:

//若是值不为null,orElse方法返回Optional实例的值。
//若是为null,返回传入的消息。
//输出:There is no value present!
System.out.println(empty.orElse("There is no value present!"));
//输出:Sanaulla
System.out.println(name.orElse("There is some value!"));

orElseGet

orElseGet与orElse方法相似,区别在于获得的默认值。orElse方法将传入的字符串做为默认值,orElseGet方法能够接受Supplier接口的实现用来生成默认值。示例以下:

//orElseGet与orElse方法相似,区别在于orElse传入的是默认值,
//orElseGet能够接受一个lambda表达式生成默认值。
//输出:Default Value
System.out.println(empty.orElseGet(() -> "Default Value"));
//输出:Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));

orElseThrow

若是有值则将其返回,不然抛出supplier接口建立的异常。

在orElseGet方法中,咱们传入一个Supplier接口。然而,在orElseThrow中咱们能够传入一个lambda表达式或方法,若是值不存在来抛出异常。示例以下:

try {
  //orElseThrow与orElse方法相似。与返回默认值不一样,
  //orElseThrow会抛出lambda表达式或方法生成的异常
  empty.orElseThrow(Exception::new);
} catch (Throwable ex) {
  //输出: No value present in the Optional instance
  System.out.println(ex.getMessage());
}

map

若是有值,则对其执行调用mapping函数获得返回值。若是返回值不为null,则建立包含mapping返回值的Optional做为map方法返回值,不然返回空Optional

map方法用来对Optional实例的值执行一系列操做。经过一组实现了Function接口的lambda表达式传入操做。map方法示例以下:

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值建立新的Optional实例做为map方法的返回值。
Optional<String> lowerName = name.map((value) -> value.toLowerCase());
System.out.println(lowerName.orElse("No value found"));

flatMap

若是有值,为其执行mapping函数返回Optional类型返回值,不然返回空Optional。

flatMap与map(Funtion)方法相似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值建立新的Optional实例做为map方法的返回值。
//但flatMap方法中的lambda表达式返回值必须是Optionl实例。
Optional<String> lowerName = name.flatMap((value) -> value.toLowerCase());
System.out.println(lowerName.orElse("No value found"));

filter

filter个方法经过传入限定条件对Optional实例的值进行过滤

filter须要传入一个lambda表达式。对于filter函数咱们应该传入实现了Predicate接口的lambda表达式。

//filter方法检查给定的Option值是否知足某些条件。
//若是知足则返回同一个Option实例,不然返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 4);
System.out.println(longName.orElse("The name is less than 4 characters"));//输出乐猿社区
 
//另外一个例子是Optional值不知足filter指定的条件。
Optional<String> anotherName = Optional.of("乐猿");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//输出:name长度不足4字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

总结

经过filter,map 和 flatMap之类的函数能够将其安全的进行变换,最后经过orElse系列,get,isPresent 和 ifPresent将其中的值提取出来,从而避免了NullPointerException,让咱们的代码看起来更加优雅

写在最后的

Optional只是Java函数式编程的冰山一角,须要结合lambda、stream、Funcationinterface等特性才能真正的了解Java8函数式编程的效用。

鼓励把新的Java8特性引入到目前的项目中,一个长期配合的团队以及一门古老的编程语言都须要不断的注入新活力,不然不进则退