序言
在我们了解这个Optional的用法之前,我想阐述下我为啥要写这个博客。
我之前和大多数的初级开发同学的一样,对java的一些比较新的语法不太重视,总认为一些基本的语法会使用就够了,比如(if、switch while、for等),再加点集合的知识,用于日常开发完全足够,其他花里胡哨的语法完全就是多此一举,比如Optional,lambda、stream等。
的确!一些常用的语法确实完全能够应付日常开发的逻辑,无非就是多写几行代码,多加几个判断。但是这里存在几个问题:
- 我们怎样能够简化代码,突出核心逻辑。比如,你想写一篇微小说,怎么能够用一句话或者几个成语交代一个角色的背景,而不是啰嗦一大堆白话废话,然后通过背景,突出他做某件事的动机。这样读者才能够让这个角色给读者留下更深的印象。Optional就为我们解决这个问题的。在开发中,我们完全可以通过一些简单(注意是简单不是简洁)的语法替代Optional的一些Api的功能,但是Optional能够帮我们更加简洁的搞定那些冗杂的判断逻辑等(比如判空,空之后返回一个新的对象或者抛异常等)。这样不仅能够让自己的代码更加简洁,而且能够提高我们的开发效率。
- 我们怎么能够尽量减少代码异常的概率。我们平时自己手写一堆判断,不仅代码看起来多,而且乱。甚至一些地方判断着判断着就忘了加一些必要的判断导致空指针异常或者其他异常,然后debug一个个去找是哪里忘记做判断和限制了。虽然Optional也有可能会导致相关的问题,但是相比较而言,你用好了Optional,能够大大的增加代码的健壮性和可读性。
- 怎么看懂别人调用了相关api的代码的逻辑 。根据我这几年做开发的经验来看,我看到很多有经验的大佬都喜欢用Optional做判断或者过滤等。而Optional毕竟是一个个Api调用,咱们不去熟悉他的Api用法和特性,很难去看懂别人写的代码的意思。为了迎合"时代发展规律",建议将Optional这个Api相关的语法摸得越清楚越好。
- …可能还有其他的有点,想到再添加吧,反正建议新手在上真战场时候,尽量用好Optional,lambda、stream等,要和使用集合一样熟练,因为日常开发的大部分时间都是做这些工作。
一、什么是Optional
来源:到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Java8引入了一个名为 Optional 的新类,它是一个容器,可以保存单个值或根本不保存任何值(null)。Optional目的是提供一种更优雅的方式来处理 null 值,或者对null值进行判断。
说简单点,就是我们创建一个对象,但是对象有可能为空,对象的各个属性有可能为空。我们平时可能需要去判断该对象是否为空,属性值是否为空的情况等,但是我们可以将这个对象传递给Optional。通过Optional这个对象的api来操作这个实际我们需要的对象即可。这样就不需要写一大堆判断逻辑了。甚至,如果我们结合lambda,好几行的判断代码一行就能搞定。
二、Optional 的各种操作
测试类entity:
@Data
@ToString
public class MyUser{
private String name; //姓名
private int age;//年纪
private String[] hobby;//爱好
}
//下面都是以这两个对象为基准
MyUser myUser1 = new MyUser("kunkun",18,new String[]{"唱","跳","rap"});
MyUser myUser2 = new MyUser("tom",12,new String[]{"篮球"});
2.1 创建(of(obj),ofNullable(obj),empty() )
要创建Optional对象,可以调用它的静态方法of() ,也可以ofNullable(),也可以empty()
Optional<MyUser> optional = Optional.of(myUser1 );//传入的对象不能为空,为空则报错
//传入的对象可为空,也可不为空,都行,后面可以用相关api判断具体是否为空,然后再做相关的操作。
//注意如果传入的为null再去get就会出异常,而不是返回null(这里好像就是用其他的异常解决了空指针异常,哈哈哈)
//这里可以通过isPresent() 判断去避免,或者用elseGet()去避免
Optional<MyUser> emptyOptional = Optional.ofNullable(null);
//创建一个没有包含对象的Optional对象
Optional<MyUser> emptyOptional1 = Optional.empty();
2.2 获取Optional 的值(获取放入的对象,get())
要访问Optional对象内部的值,可以调用它的 get ()方法。但是,如果Optional项为空,不会去返回null,而是会引发NoSuchElementException异常。
Optional<MyUser> optional = Optional.of(myUser);
MyUser myUser = optional.get(); // 返回的结果为MyUser的对象
Optional<MyUser> emptyOptional = Optional.ofNullable(null);
String nullValue = emptyOptional.get(); // throws NoSuchElementException
2.3 获取Optional 的值2 (orElse(obj) , orElseGet(Supplier s),判空之后自定义条件返回)
为了避免上面为空时候出异常的问题,这里有一个条件返回的API-------为空时候返回啥
//T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
//存入的不为空的情况
Optional<MyUser> optional = Optional.ofNullable(myUser1);
//打印myUser1的值:MyUser(name=kunkun, age=18, hobby=[唱, 跳, rap])
//因为这里传入得到myUser1不为空
System.out.println(optional.orElse(myUser2));
//----------------------------------------------------------------------------------
Optional<MyUser> optional1 = Optional.ofNullable(null);
//打印myUser2的值:MyUser(name=tom, age=12, hobby=[篮球])
//因为这里传入的值为空,返回myUser2
System.out.println(optional1.orElse(myUser2));
上面的条件返回总显得很单一,就仅仅能把为空的值做个非空的替换而已,那有个场景:如果获得的为空,我们需要重新再数据库里查询,然后过滤,返回一个叫tom的对象,这应该怎么通过简单的方式实现呢。别急,Optional海提供了第二种条件获取的方法orElseGet。
// T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
Optional<MyUser> optional = Optional.ofNullable(null);//传入的对象不能为空,为空则报错
MyUser tom = optional.orElseGet(new Supplier<MyUser>() { //注意这里和上面的api不是重载的 一个是orElse,一个是orElseGet
@Override
public MyUser get() {
//查询数据库操作,过滤出tom的值,然后返回
return null;
}
});
System.out.println(tom); //打印tom的值:MyUser(name=tom, age=12, hobby=[篮球])
//这里可以将其转换为lambd表达式,看着更加简洁,这里就不做赘述了
假如我们没有获取到自己想要的对象,也就是为空,我们想要抛出自己自定义的异常咋办呢,根据if判断然后抛出异常? 这样确实可以,但是不还是陷入if判断的尴尬情况了吗。Optional为我们提供了更加简洁的实现方式 orElseThrow,运用lambda表达式,可以直接一行就搞定,当代码量特多的时候,你会发现lambda,optional等真的能够大大简化代码。
// T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
Optional<MyUser> optional = Optional.ofNullable(null);//传入的对象不能为空,为空则报错
optional.orElseThrow((Supplier<Throwable>) () -> new Exception("自定义异常"));
2.4 Optional 非空判断以及非空表达式(isPresent()、ifPresent() )
Optional 类有一个判断该optional是否包含对象的方法isPresent(),他的作用类似于集合的isNotEmpty()的方法,判断该optional是否包含对象。他的使用如下:
Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
System.out.println(optOrNull.get().length());
}
是不是看起来有点麻烦,这样就又陷入了if判断导致多行代码的问题了,使得代码不够简洁。有没有用lambda表达式的方式呢? 显然是有的,Optional给我们提供了一个叫ifPresent() 的方法,该方法允许我们使用函数式编程的方式判断该Optional里面是否存在对象。存在就执行自定义的函数式接口的实现。我们把它称为非空表达式。如果没有该方法的话,我们通常需要先通过 isPresent() 方法对 Optional 对象进行判空后再执行相应的代码。使用如下:
// void ifPresent(Consumer<? super T> consumer):存在执行相关的函数式接口
Optional<String> optOrNull = Optional.ofNullable(null);
optOrNull.ifPresent(new Consumer<MyUser>() {
@Override
public void accept(MyUser myUser) {
//此时这里不会执行,因为optOrNull 是空值
System.out.println("该optional存在一个对象,myUser" + myUser);
}
});
//----------------------------------------------------------------------------------
Optional<String> opt = Optional.of(null);
opt.ifPresent(new Consumer<MyUser>() {
@Override
public void accept(MyUser myUser) {
//此时这里会执行,因为opt 不是空值
System.out.println("该optional存在一个对象,myUser" + myUser);
}
});
2.5 过滤值( filter(Predicate predicate) )
我们可以对该对象进行过滤,自定义Predicate 的过滤条件,当返回为真(true) 时候,原封不动的返回该Optional,当Predicate 返回为false时候,则返回一个空的Optional(不是返回null,而是返回一个Optional.empty()),然后我们可以进一步做操作,链式编程。
具体的用法如下:
// Optional<T> filter(Predicate<? super T> predicate)
Optional<MyUser> optional = Optional.of(myUser1);
Optional<MyUser> optional1 = optional.filter(new Predicate<MyUser>() {
@Override
public boolean test(MyUser myUser) {
return myUser.getAge() > 18;
}
});
//此时optional1 的值是空的Optional,因为kunkun刚好18 条件为false
2.6 转换值(map(Function mapper) ,flatMap(Function mapper))
当我们想将这个Optional里的对象替换为其他的对象时,我们可以用Map做转换。举个例子:
我们想把MyUser里的名字提取进行添加前缀,再返回名称字符串,然后打印出来,用get() 然后getName()再拼接打印就可以实现,但是这样又会显得代码好几行,有没有更简单的方法呢?一行就搞定的(前提是你需要用lambda表达式的简化):
//Optional<U> map(Function<? super T, ? extends U> mapper)
Optional<MyUser> optional = Optional.of(myUser1);
Optional<String> optName = optional.map(new Function<MyUser, String>() {
@Override
public String apply(MyUser myUser) {
//可以在这里做一系列的逻辑判断
return "姓名:" + myUser.getName();
}
});
//返回的optName对象还可以进行链式编程,形成多个过滤条件,链式过滤。
System.out.println(optName.get()); //打印出:姓名:kunkun
这样看起来代码好像更加多了。我这里只是为了让读者看的更加清晰,所以没做lambda表达式的简化。且,当有多种转换条件叠加时,更能够凸显出Optional的简化,例如:
Optional<MyUser> optional = Optional.of(myUser1);
String endVal = optional
.map(myUser1 -> "姓名:" + myUser1.getName()) //一次转换
.map(s -> {//二次转换
System.out.println(s + "二次过滤");
//kunkun替换为汉语(这里为了简化直接写死为 "姓名:坤坤"
return "姓名:坤坤";
}).get();
System.out.println(endVal);
是不是看起来就简洁多了,如果用常规的方式去写,可能还得加上多个set get等逻辑判断等。总的来说,处理链越多,越能凸显Optional的优势。
而 flatMap的区别和map的区别是啥呢?
区别如下:
Optional<MyUser> o1 = optional.flatMap(new Function<MyUser, Optional<MyUser>>() {
@Override
public Optional<MyUser> apply(MyUser aa) {
// 它返回的必须是一个Optional的值,疑问它不会帮你自动套一个Optional
// 这样能够防止Optional套Optional,
// 当然你非要在Optional.of(Optional.of(new MyUser()))这样套也没法避免,
return Optional.of(new MyUser());
}
});
//------------------------------------------------------------------------------------------------------
Optional<String> o2 = optional.map(new Function<MyUser, String>() {
@Override
public String apply(MyUser myUser) {
// 它会自动在你返回的值之后套入一个Optional,
// 如果你这边和上面一样 return Optional.of(new MyUser())。那就会出现Optional套住Optional的情况。
return "aaaaa";
}
});
总的来说,其实两者区别不大,只是一个返回时自动的帮你套了一个Optional,一个必须你自己亲自套。
三、结束
optional的操作方法总的来说其实不多,但是运用的非常广,特别是开发的时候,如果小伙伴在开发的时候,可以尝试将一个判断之类的改成optional,慢慢习惯它,这样以后在链式处理,lambda等处理上代码会变得更加简洁优雅。