有,或者没有,这是一个问题
只要是做过Java开发的人就肯定会跟异常打交道,而我猜其中很大一部分人的第一次,肯定都送给了NullPointerException
。空指针异常,再简单不过的一个异常,当你引用了一个空对象,并试图去使用它里面的方法或者属性时,bazinga!!!于是乎,为了避免掉坑里,就有了下面这样的写法。
@Data
public class SoccerTeam {
private String name;
private Coach coach;
}
@Data
public class Coach {
private String name;
private Assistant assistant;
}
@Data
public class Assistant {
private String name;
}
public String getTeamCoachAssistantName(SoccerTeam team) {
if (team != null) { // 空检查
Coach coach = team.getCoach();
if (coach.getAssistant() != null) { // 空检查
return coach.getAssistant().getName();
}
}
return "Unknown";
}
还是熟悉的配方,一样熟悉的味道。遇到层级更多的时候,我相信不只有我自己一个人想把那段代码都删掉。为什么会出现这样的代码呢,绝大部分情况下,是因为我们不知道这个返回值是否会为空,而为了避免运行时产生空指针异常,我们就只能在调用这个对象之前迫使自己做一遍检查。除了像这样防御式的检查,我们还有没有别的形式来解决这个问题呢,Java 8中给了我们另外一种选择,使用java.util.Optional<T>
。
Optional简介
Optional它实际上只是对你要用到的对象进行一个简单的封装,而它本身就是用来表示这个对象有可能存在,也有可能不存在。当对象存在时,你可以通过它提供的方法获取到该对象,当对象不存在时,它也会返回一个EMPTY
对象,而不是一个null
,从而避免了空指针异常。于是,上面的代码就可以改成下面这样。
@Data
public class SoccerTeam {
private String name;
private Coach coach;
}
@Data
public class Coach {
private String name;
private Optional<Assistant> assistant;
}
@Data
public class Assistant {
private String name;
}
public String getTeamCoachAssistantName(SoccerTeam team) {
Optional<Assistant> assistant = team.getCoach().getAssistant();
if (assistant.isPresent()) {
return assistant.get().getName();
}
return "Unknown";
}
咋一看,上面的代码和原来其实区别并不大,只是将Coach中的Assistant换成了Optional
在第一版代码中,每个对象中,都是引用了原始类型的对象,这就会使得你从代码层面并不知道这些对象是否是必须存在的,你只能根据自己对业务逻辑的理解来判断他们是否需要检查null值,或者更多的人会在每一处都进行null检查。
而在第二版代码中,Coach类下面明确使用了Optional<Assistant>,这也就从语义层面就明确的告诉了你,教练可能会有助手,也可能没有助手,所以你应该在这里明确进行不同的处理。而其他直接使用原始类型的地方,也就表明了那个对象是必须存在的,你不应该对它做null判断,从而将对象为null的问题掩盖掉。
当你在代码中使用Optional之后,能非常清晰地界定出变量值的缺失是结构上的问题,还是你算法上的缺陷,抑或是你数据中的问题。另外,引入Optional类的意图并非要消除每一个null引用。与此相反,它的目标是帮助你更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional的值。这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值。
而当你真正了解使用Optional的目的之后,我们再来看看应该如何去使用它。
创建Optional
有三种创建Optional的方式,分别是创建一个空的Optional、根据一个非空值创建Optional和可接受null的Optional。
// 创建一个空的Optional
Optional<?> empty = Optional.empty();// Optional.empty
// 根据一个非空值创建Optional
Optional<String> strOpt = Optional.of("Hello Java 8");// Optional[Hello Java 8]
// 如果传入null会报空指针异常
Optional<String> strOpt2 = Optional.of(null);// error! NullPointerException!
// 可接受null的Optional
Optional<String> nullOpt = Optional.ofNullable(null);// Optional.empty
从Optional提取值
Optional类提供了很多种方式来获取它里面的对象,接下来我们就来一个一个的进行介绍。
首先,最简单的一个就是使用get()
方法取值,如果对象存在的话,它会返回对象,如果对象不存在的话,它会抛出一个NoSuchElementException
异常。除非你是确定Optional里面有值存在,不然一般情况都不建议使用该方法。
Optional<String> empty = Optional.empty();
Optional<String> strOpt = Optional.of("hello");
// 使用get方法
System.out.println(empty.get());// error! NoSuchElementException!!
System.out.println(strOpt.get());// hello
使用orElse(T other)
方法取值。这个方法会让你填一个默认值,当Optional中的对象存在时,会返回该对象,当Optional中的对象不存在时,会返回参数中这个默认值。
// 使用orElse(T other)方法
System.out.println(empty.orElse("Default String"));// Default String
System.out.println(strOpt.orElse("Default String"));// hello
使用orElseGet(Supplier<? extends T> other)
方法取值。该方法是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。如果创建默认值是件耗时费力的工作,你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式。
// 使用orElseGet(Supplier<? extends T> other)方法
System.out.println(empty.orElseGet(() -> "Default String"));// Default String
System.out.println(strOpt.orElseGet(() -> "Default String"));// hello
使用orElseThrow(Supplier<? extends X> exceptionSupplier)
方法取值。改方法和get方法非常类似, 它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。
// 使用orElseThrow(Supplier<? extends T> other)方法
System.out.println(empty
.orElseThrow(() -> new IllegalArgumentException("字符串不能为空")));// error! IllegalArgumentException!!
System.out.println(strOpt.orElseThrow(() -> new IllegalArgumentException("字符串不能为空")));// hello
使用ifPresent(Consumer<? super T>)
方法,它不能获取值,但是能让你在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
// 使用ifPresent(Consumer<? super T>)方法
empty.ifPresent(System.out::println);// 没有任何输出
strOpt.ifPresent(System.out::println);// hello
使用map和flatMap来提取和转换值
Optional还可以像使用流一样的来使用map
操作,你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。我们可以将最开始的例子变成下面这样。
// 使用map方法
return team.getCoach().getAssistant().map(Assistant::getName)
.orElse("Unknown");
因为map方法会返回一个Optional对象,假如我们将上面的例子改一下,一个球队里面的Coach也是Optional的对象,这时候我们就不能够通过map方法来进行连接,而是需要用到flatMap
方法。
@Data
public class SoccerTeam {
private String name;
private Optional<Coach> coach;
}
public String getTeamCoachAssistantName(SoccerTeam team) {
return team.getCoach().flatMap(Coach::getAssistant)
.map(Assistant::getName).orElse("Unknown");
}
两个Optional对象的组合
可能你还会遇到这样的一种情况,如果同时有两个Optional参数,只有他们都存在的情况下,才需要对他们进行处理,否则就返回一个空对象。可能你立即会考虑到像下面这样做:
// 通过速度和时间来计算距离
Optional<Integer> speed = Optional.of(110);
Optional<Integer> time = Optional.of(120);
Optional<Integer> distance = Optional.empty();
if (speed.isPresent() && time.isPresent()) {
distance = Optional.of(speed.get() * time.get());
}
distance.ifPresent(System.out::println);
又是通过if来做判断,其实利用flatMap和map方法,你可以像使用三元操作符那样,无需任何条件判断的结构,以一行语句实现该方法,就像下面这样:
// Optional组合方式
Optional<Integer> distance = speed.flatMap(s -> time
.map(t -> calDistance(s, t)));
当speed和time其中任何一个为空时,flatMap和map方法都不会执行,而是直接返回一个empty对象,只有当两者都存在时才会进入map方法中,调用calDistance方法来计算距离。
使用filter剔除特定的值
最后,你还可以使用filter
方法,就像使用流一样,对Optional中的对象进行过滤。例如,我们只需要获取教练名字叫做瓜迪奥拉的教练的助手的名字,就可以像下面这样进行过滤:
public String getTeamCoachAssistantName(SoccerTeam team) {
return team.getCoach().filter(c -> "瓜迪奥拉".equals(c.getName()))
.flatMap(Coach::getAssistant).map(Assistant::getName)
.orElse("Unknown");
}
总结语
本文主要介绍了Optional
的使用方法,大家最重要的是应该先搞明白为什么要使用它,就像文章最开始所说的,引入Optional
类的意图并非要消除每一个null
引用,而是可以帮助你更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional
的值。而一旦开始使用它之后,也将会把你从无限的null
判断中解放出来。
本文案例源码:
https://github.com/lanheixingkong/HelloJava8
参考资料:
- 《Java 8实战》
PS:很难得,今天的脑子里面居然一首歌曲都没有出现,既然写的东西叫做Optional,那就来一首类似的音乐吧。不知道,现在的你,是忧伤还是快乐呢。。。
0条留言