在开发过程中,空指针异常是最常见,不过也是比较容易修改的。尽管如此,为了避免空指针,我们可能会加入大量的检测逻辑。好在Java8中为我们提供了Optional类,它拥有一整套完善的为空检测及处理逻辑,大大的方便了我们的开发。
Optional类实际上就是一个容器,里面保存着我们的对象,并提供取方法,并且可以为存为null的对象。
创建一个Optional对象:
1.Optional.of(obj)
这个方法需要传入一个非空的对象
2.Optional.ofNullable(obj)
这个不同于上面的方法,它允许传入一个为空的对象
3.Optional.empty()
严格意义来说,这个并不算,他是返回一个空的Optional实例,注意,并不是为null的实例,而是不包含其他对象的实例。源码如下:
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
具体使用
获得了一个Optional实例后,关键就是怎么好好使用。首先这个类提供了一个为空判断方法:isPresent(),但是千万不要单纯的使用这个方法,比如:
private void test(Person person){
Optional<Person> optional = Optional.ofNullable(person);
if(optional.isPresent()){
//...
return;
}
//...
}
那简直是对这个类的一大浪费,而且又无故多写了许多代码。我们可以看一下isPresent()的实现:
public boolean isPresent() {
return value != null;
}
这不就是我们经常写的为空判断么。而且大致看一眼这个类的方法,也能感觉到它不仅仅只是做一个判断这么简单。接下来,我们就一起来学习一下其他那些非常有用的方法,之后你也许就会感受到设计人员的良苦用心了。
1.get()
首先我们来看看取对象的方法,在以此为扩展了解其他方法。Optional既然是一个容器,那么肯定能存能取,不然要他干嘛?
这个方法语义很清晰,就是取之前我们保存的对象,当对象为空时,抛出空指针异常。可能有人要问了,既然只是简单的取值,而且还是会抛异常,哪有何用?对,这个方法本身并没啥用,就是一个简单的get方法,除非你配合isPresent(),但那样又回到了以前的思维,所以我们主要来看后面的方法。
2.orElse(T other)
这个就比较使用了,他要求传入一个默认值,当我们之前保存的值为空时,就返回传入的默认值,否则返回之前保存的值。
public static void main(String[] args){
test(null); //10
test(new Person(15)); //15
}
public static void test(Person person){
Optional<Person> optional = Optional.ofNullable(person);
out.println(optional.orElse(new Person(10)).getAge());
}
帮我们省略了一些if-else逻辑,是不是很人性化?不要急着满足,后面还有更精彩的。
3.orElseGet(Supplier<? extends T> other)
这个与上面类似,只不过为空时不再返回一个默认值,而是通过提供的方法,返回这个方法所返回的值:
public static void main(String[] args){
test(null); //10
test(new Person(15)); //15
}
public static void test(Person person){
Optional<Person> optional = Optional.ofNullable(person);
out.println(optional.orElseGet(()->new Person(10)).getAge());
}
(为了简洁,我一般都用lambda表达式形式书写代码,如果对lambda表达式不熟悉的,可以参考我的这篇文章)
4.ifPresent(Consumer<? super T> consumer)
一般来说,我们进行为空判断后肯定要接着执行一些操作。所以,设计人员为我们提供了这个方法,基本功能就是如果我们所存的对象不为空时,执行所指定的代码,为空时什么都不做:
public static void test(Person person){
Optional<Person> optional = Optional.ofNullable(person);
optional.ifPresent(p -> out.println(p.getAge()));
}
这样一来为空判断和后续逻辑就连贯在一起了。
5.map(Function<? super T,? extends U> mapper)
有时候,我们从某个不为空的对象上使用某些方法后,也会生成空对象,因此就要要连续多级连续判断,放在以前,就要大量if-else嵌套,而map函数很方便这些操作:
optional.map(p -> p.getName())
.map(n -> n.toUpperCase())
.orElse("ABC")
换成等价的if-else代码
if (person != null) {
String name = person.getName();
if (name != null) {
out.println(name.toUpperCase());
}else{
out.println("ABC");
}
}else{
out.println("ABC");
}
map和ifPresent类似,也是在不为空时执行指定逻辑,只不过map返回的也是一个Optional,所以map链可以无限延长,代替了if嵌套if的语句,不仅简单而且结构清晰。如果某一环为空,则不会执行所指定的逻辑,返回一个空的Optional实例。所以我们可以在某一环添加一些其他的方法,进行为空时的逻辑处理。类似的自由组合可以创造无限可能。
6.filter(Predicate<? super T> predicate)
了解过流式编程的朋友看到Optional的这些方法可能会很熟悉(如果不熟悉也没关系,在下一篇文章中学习过Stream类后,你就会很了解这类编程风格),这些方法应该是这一类API的标准方法,所以怎能少的了filter?
顾名思义,这个方法就是过滤的意思,只有符合我们所指定的逻辑后,才会向下传递,否者传递一个空的Optional,当然,身为空指针检测类,他的前提条件当然是不为空,否则不会执行传入的代码:
optional.filter(p -> p.getAge()>18).orElse(new Person(19, "jack")).getAge()
这个就很方便我们在不为空的同时进行逻辑判断,同时由于filter也是返回一个Optional对象,所以也可以搭配其他方法使用。
7.flatMap(Function<? super T,Optional<U>> mapper)
这个方法名字中也有一个map,那么应该可map有关联。对的,他的功能和map差不多,都是类似转换功能,但是,map的返回值是自动帮我们包装好的Optional对象,而flatmap这需要我们自己包装一个Optional对象:
optional.flatMap(p -> Optional.ofNullable(p.getName()))
可见flatMap灵活性较map大一点,不过map既然这样设计也是方便我们开发,不一定每次为空检测后,都要返回一个不相干的类型,所以如何取舍,还是自己决定。ψ(*`ー´)ψ
8.orElseThrow(Supplier<? extends X> exceptionSupplier)
Optional类的方法并不多,到这里我们已经讲得差不多了,但是是不是觉得少点什么呢?原来前面的方法基本上都是检测为空时,直接不执行了,这样虽然不会报错,但是对于我们后期维护检测问题并不好。
Optional.of虽然会抛异常,但是他是在一开始包装的时候就抛,如果这个对象不为空,对象的成员为空呢?总不能还要用get()方法吧,get虽然可以,但只是抛一个传统的空指针异常,如果要抛自定义异常呢?
这时候就要用orElseThrow了,他在为空时会抛一个我们指定的异常,由于传递的是代码块,所以我们也可以在抛异常前,执行一些其他逻辑:
optional.orElseThrow(()->{out.println("hello");return new Throwable("abc");});
小结
这个类主要功能就是空指针检测,只不过设计人员为我们添加了许多实用的扩展方法,节省了我们大量代码,总体来说是很不错的。有兴趣的朋友可以看一看这个类是怎么实现的,由于是相对独立的,所以理解起来难度并不高。而且这个类的方法都是比较简单地,基础好的朋友也许自己都能创造出类似的类。
总之,既然为我们提供了这么实用的工具,我们就要在以后的实践中尽量有意识的多用用,不要再像以前一样if…else… ๑乛乛๑