正确使用Java8中的Optional,它远比我们想象的优秀
2023-01-09 13:13:49来源:今日头条
我常说学习一定要有目的,首先发现问题,或者不便之处,然后寻找解决方案,解决方案可能有很多,我们要选择好的方法来使用
这篇文章介绍JDK8推出的Optional容器,会从以下几点展开:
现在编程的问题或者说痛点是什么通过案例演示:解决方案有哪些,Optional怎么解决Optional系统用法Optional的错误用法Optional总结由此一起来认识Optional的正确使用方式,远比我们想象的强大,好用,看很多文章和教程都在讲API,个人感觉调用一个方法谁不会?它到底好在哪才是最重要的,我发布的文章都秉承发现问题,解决问题的理念展开,好了,不吹了,精彩的要来了!
(资料图片)
作为Java程序员遇到NullPointerException是非常痛苦的,这可能是我们遇到的最多的异常了
前后端联调:嗨!哥们,你这500啥意思呀?
后端:先是沉思,这怎么会有空指针?对前端说:哥们等1分钟,马上解决,我可不能说空指针,我可是老开发了!说空指针多没面子。
产生过这种无奈的请在评论区大声说出来!无论是新手还是专家,在NullPointerException面前可谓众生平等
我们编程时经常承受:写了类型检查,值判断,最终没想到竟然是一个null的痛苦,毫不留情的甩出来一个令人厌烦的NullPointerException,比如:
系统中用户,有些用户进行了实名认证,拥有身份证信息,有些用户并没有完成实名认证就没有身份证信息【不要深究设计是否合理,仅仅是举例讲解Optional知识点】
用户类:
public class User { private Long id; private String name; // 身份证对象 private IdCard idCard; // getter、setter、toString}
身份证类:
public class IdCard { private Long id; // 身份证号码 private String idNum; // getter、setter、toString}
测试类:获取用户的身份证号码
public class OptionalMain { public static void main(String[] args) { // 创建用户对象 User user = new User(); // 调用一系列get方法获取身份证号码 // 因为调用 getIdCard()时并没有身份证对象为null,再调用getIdNum方法则出现 NullPointerException String idNum = user.getIdCard().getIdNum(); System.out.println(idNum); }}
运行结果:
如果user是传递进来的,传进来的user也有可能是null
解决方案怎样做才能避免不期而至的NullPointerException?通常,在需要的地方添加null的检查,所以我们的代码多了很多的判断是否为null的验证,影响代码结构,甚至有时不加思索是否需要验证也会统一加上非空判断,来避免不可预知的空值,防止生产环境造成损失!并且添加的方式往往各有不同:
嵌套判断:
public class OptionalMain { public static void main(String[] args) { User user = new User(); // 判断user是否为null if(user != null) { IdCard idCard = user.getIdCard(); // 判断 idCard 是否为null if(idCard != null) { // 获取身份证号码 System.out.println(idCard.getIdNum()); }else { System.out.println("未实名认证!"); } }else { System.out.println("该用户不存在!"); } }}
逐个判断:
public class OptionalMain { /** * 获取身份证号码 * @param user:用户 * @return:身份证号码 */ public static String getUserIdcardNum(User user) { // 判断用户是否为空 if(user == null) { return "无此用户"; } // 判断是否实名认证 if(user.getIdCard() == null) { return "该用户未实名认证"; } // 返回身份证号码,如果:要对身份证号码进行操作,也要对idNum进行非空判断 return user.getIdCard().getIdNum(); } public static void main(String[] args) { // 创建用户对象 User user = new User(); // 1、调用获取身份证方法,有用户但未实名 System.out.println("******未认证******"); String userIdcardNum1 = getUserIdcardNum(user); System.out.println("结果:" + userIdcardNum1); // 2、传递空用户 System.out.println("******空用户******"); String userIdcardNum2 = getUserIdcardNum(null); System.out.println("结果:" + userIdcardNum2); // 3、创建身份证对象 IdCard idCard = new IdCard(); idCard.setId(1L); idCard.setIdNum("411481199611111516"); user.setIdCard(idCard); // 传递实名认证的用户 System.out.println("******已认证******"); String userIdcardNum3 = getUserIdcardNum(user); System.out.println("结果:" + userIdcardNum3); }}
运行结果:
如果有其他要求,就要做更多的非空判断,影响代码的连贯性,净判断空值了
一旦忘记判断某一个值是否为空,就又要和 NullPointerException 偶遇了,它并不是女朋友,而是最不想遇见的【债主】
null值带来的问题NullPointerException是目前Java程序开发中最典型的异常,有些书中称其为错误之源,个人觉得有点夸张,你觉着呢?各种非空判断,让代码变的冗余,阅读性很糟糕,非空判断对业务实现是毫无意义的null值本身也毫无意义,可以认为是给对象一个【错误的默认值】null可以被赋值给任意的引用数据类型,如果是分布式系统,该值被传递到另一个服务中,无法知道最初的它是什么类型,也无法对其进行赋值Java为了简化语言,摒弃了指针的概念,但是NullPointerException是个例外Optional的出现Java团队结合Haskell和Scala语言对null值的处理方式,在JDK8时推出Optional类来专门处理空值问题,当然该类并不是为了避免我们去写!=null的非空判断,他功能很强,配合Lambda表达式更香
源码注释/** * A container object which may or may not contain a non-null value. 一个可以包含或不包含非空值的容器对象 * If a value is present, {@code isPresent()} will return {@code true} and * {@code get()} will return the value. 如果存在值,isPresent()方法会返回true,通过get()方法返回值 * *Additional methods that depend on the presence or absence of a contained * value are provided, such as {@link #orElse(java.lang.Object) orElse()} * (return a default value if value not present) and * {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block * of code if the value is present). 提供了取决于是否存在包含值的其他方法,比如orElse,如果值不存在,则返回默认值 并且 可以通过ifPresent() 判断值是否存在,存在则执行代码块 *
This is a value-based * class; use of identity-sensitive operations (including reference equality * ({@code ==}), identity hash code, or synchronization) on instances of * {@code Optional} may have unpredictable results and should be avoided. 这是一个基于值的类,应避免使用于身份敏感操作【这里应该意思是:对象是否存在不确定的敏感操作】(包括引用 ==,哈希或同步)的实例可能会产生不可预测的结果 * @since
从Optional类的定义和声明来看特点如下:
是一个final类,不可被继承,并且是一个泛型类该类是一个容器,可以用来存储对象该类提供了一系列方法来判断是否有值【isPresent()】和获取值【get()】Optional解决null问题通过案例感受Optional处理null的套路:
将可能为null,或者说允许为null的数据存储进Optional容器中通过Optional的map、filter、flatMap方法对数据进行处理,获取需要的对象属性,用法和Stream相同如果数据为空了,可以返回一个自定义对象,或者抛出异常都可以,随你所愿User类:
public class User { private Long id; private String name; // 将可能为null的对象放入Optional中 private OptionalidCard; // getter、setter、toString}
IdCard类:
public class IdCard { private Long id; // 如果身份证号码也允许为null,也可以放入Optional中【Optional】 // 但是实名认证了,身份证号码就是必须的了不是吗, // 一旦使用了Optional,没有身份证号码时,也不会出现报错,可能会出现数据错误,所以也不要滥用 private String idNum; // getter、setter、toString}
测试类:
public class OptionalMain { /** * 获取身份证号码 * @param user:用户 * @return:身份证号码 */ public static String getUserIdcardNum(User user){ // 将User通过Optional.of() 方法 存储进Optional OptionaloptionalUser = Optional.of(user); // 通过map方法先获取user中身份对象,orElse:如果没有,返回一个自定义的Optional 对象 Optional optionalIdCard = optionalUser.map(User::getIdCard).orElse(Optional.of(new IdCard())); // 通过map方法获取IdCard中的idNum,如果没有返回 "无实名认证"字符串 String idNum = optionalIdCard.map(IdCard::getIdNum).orElse("无实名认证"); return idNum; } public static void main(String[] args){ User user = new User(); // 将user对象传进方法中,该对象中的IdCard为null System.out.println(getUserIdcardNum(user)); }}
运行结果:
我们仅仅传入了user对象,IdCard为null,通过getUserIdcardNum方法处理之后,返回定义的无实名认证,这里并没有做if...else的判断,这样的代码看起来更优雅,不是吗?
总结来说:
把对象放进Optional中,可以通过Optional提供的API来操作容器中的对象如:对象非空正常使用,我们可以通过get()方法获取对象如果是空可以通过某些方法【orElse、orElseGet、orElseThrow】,返回自定义的结果,避免空指针异常出现通过一些判断方法来判断Optional中对象是否为null接下来讲解一下Optional中的API,系统认识,学习强大的Optional
Optional结构Optional方法概览方法 | 作用 |
Optional.empty() | 创建一个空的 Optional 实例 |
Optional.of(T t) | 创建一个 Optional 实例,当 t为null时抛出异常 |
Optional.ofNullable(T t) | 创建一个 Optional 实例,但当 t为null时不会抛出异常,而是返回一个空的实例 |
get() | 获取optional实例中的对象,当optional 容器为空时报错 |
isPresent() | 判断optional是否为空,如果空则返回false,否则返回true |
ifPresent(Consumer c) | 如果optional不为空,则将optional中的对象传给Comsumer函数 |
orElse(T other) | 如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值 |
orElseGet(Supplier | 如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other |
orElseThrow(Supplier | 如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常 |
filter(Predicate | 如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional |
map(Function | 如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中 |
flatMap(Function< T,Optional> mapper) | 跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional,区别在于:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optional |
强烈建议:打开编辑器,多翻阅源码,对学习和编码都有很大帮助,刚开始看不懂没关系,量变产生质变
Optional 创建通过Optional源码发现:
该类final修饰,不能被继承,只有一个Object父类是一个泛型类,使用时为了类型安全指明泛型类型连个私有常量,供内部调用,其中value为Optional容器中存储的对象两个构造方法,无参和有参的都为私有,说明不能通过构造方法创建Optional对象,需要通过内部提供的【empty()、of(T t)、ofNullable(T t)】三个静态方法创建,这种创建方式其实就是【工厂模式】代码实现:
// 创建一个包装对象值为空的Optional对象OptionalOptional其他APIget()
作用:获取optional实例中的对象,当optional 容器为空时报错
源码:
判断value是否为null为null,抛出NoSuchElementException("No value present")异常不为null,返回valuenull值Optional:
// 创建值为null的Optional对象Optionaloptional = Optional.empty();// get返回 NoSuchElementException("No value present")String result = optional.get();System.out.println(result);
非null值Optional:
// 创建值为:optional的Optional对象OptionalisPresent()optional = Optional.of("optional");// 返回值 optionalString result = optional.get();System.out.println(result);
作用:判断optional是否为空,如果空则返回false,否则返回true
源码:
代码实现:
ListorElse(T other)users = new ArrayList<>();users.add("柯南");users.add("佩奇");users.add("喜洋洋");Optional > optional = Optional.of(users);// 判断并消费optional.ifPresent(System.out::println);
作用:如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
源码:
代码实现:
User user = new User(1L,"格雷福斯");// 1、存储非null数据OptionalorElseGet(SupplieruserOptional = Optional.ofNullable(user);// 获取用户名String name1 = userOptional.orElse(new User(0L, "帅气添甄")).getName();// 非null,结果为:格雷福斯System.out.println(name1);// 2、存储null数据Optional nullOptional = Optional.ofNullable(null);String name2 = nullOptional.orElse(new User(0L, "帅气添甄")).getName();// 为null,结果:帅气添甄System.out.println(name2);
作用:如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other
源码:
代码实现:
User user = new User(1L,"格雷福斯");OptionalorElseThrow(SupplieruserOptional = Optional.ofNullable(user);// 为null直接返回`Supplier`生产型函数接口返回的对象String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();System.out.println(name);
作用:如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
源码:
代码实现:
User user = new User(1L,"格雷福斯");OptionalorElseThrow(SupplieruserOptional = Optional.ofNullable(user);// 为null直接返回`Supplier`生产型函数接口返回的对象String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();System.out.println(name);
作用:如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
源码:
代码实现:
User user = new User(1L,"格雷福斯");Optionalfilter(PredicateuserOptional = Optional.ofNullable(user);// 如果数据为null,抛出 指定异常String name = userOptional.orElseThrow(() new RuntimeException("无数据")).getName();System.out.println(name);
作用:如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
源码:
代码实现:
User user = new User(1L,"格雷福斯");Optionalmap(FunctionuserOptional = Optional.ofNullable(user);// 过滤名字长度大于3,如果有值才输出,没值就不输出userOptional.filter(item -> item.getName().length() > 3).ifPresent(System.out::println);
作用:如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中,该方法与Stream的map作用一样
源码:
代码实现:
User user = new User(1L,"格雷福斯");OptionalflatMap(Function< T,Optional> mapper)userOptional = Optional.ofNullable(user);// 只获取用户名String name = userOptional.map(User::getName).orElse("添甄");System.out.println(name);
作用:在optional不为空的情况下,将对象t映射成另外一个optional,17-flatMapmap接收的是U类型,而flatMap接收的是Optional类型,返回也是需要放进Optional中
源码:
代码实现:
User user = new User(1L,"格雷福斯");Optional错误示范userOptional = Optional.ofNullable(null);Optional optional = userOptional.flatMap(item -> Optional.ofNullable(item.getName()));String name = optional.orElse("添甄");System.out.println(name);
获取用户名:
User user = new User(1L,"格雷福斯");OptionaluserOptional = Optional.ofNullable(user);// 判断是否有值if (userOptional.isPresent()) { String name = userOptional.get().getName(); System.out.println(name);}else { System.out.println("无值");}
通过调用isPresent方法判断是否有值,这还是增加了判断,破坏代码结构
正确姿势:
多用map,orElse,filter方法发挥Optional的作用
User user = new User(1L,"格雷福斯");Optional总结Optional是一个用来解决null值,避免发生空指针异常的容器,配合Lambda表达式写出优雅代码静态工厂方法Optional.empty()、Optional.of()以及Optional.ofNullable()创建Optional对象Optional类包含多种方法,其中map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似使用Optional能帮助你开发出更便于阅读和简介的程序多使用Optional中的方法给定默认值,比如map、orElse等方法来避免过多的if判断userOptional = Optional.ofNullable(user);String name = userOptional.map(User::getName).orElse("无值");System.out.println(name);