怎样做才能避免不期而至的NullPointerException?通常,在需要的地方添加null的检查,所以我们的代码多了很多的判断是否为null的验证,影响代码结构,甚至有时不加思索是否需要验证也会统一加上非空判断,来避免不可预知的空值,防止生产环境造成损失!
前言
我常说学习一定要有目的,首先发现问题,或者不便之处,然后寻找解决方案,解决方案可能有很多,我们要选择好的方法来使用
这篇文章介绍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()方法返回值
*
* <p>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()
判断值是否存在,存在则执行代码块
* <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
* 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 Optional<IdCard> idCard;
// getter、setter、toString
}
IdCard类:
public class IdCard {
private Long id;
// 如果身份证号码也允许为null,也可以放入Optional中【Optional<String>】
// 但是实名认证了,身份证号码就是必须的了不是吗,
// 一旦使用了Optional,没有身份证号码时,也不会出现报错,可能会出现数据错误,所以也不要滥用
private String idNum;
// getter、setter、toString
}
测试类:
public class OptionalMain {
/**
* 获取身份证号码
* @param user:用户
* @return:身份证号码
*/
public static String getUserIdcardNum(User user){
// 将User通过Optional.of() 方法 存储进Optional
Optional<User> optionalUser = Optional.of(user);
// 通过map方法先获取user中身份对象,orElse:如果没有,返回一个自定义的Optional<IdCard>对象
Optional<IdCard> 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<T> other) | 如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other |
orElseThrow(Supplier<X> exception) | 如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常 |
filter(Predicate<T> p) | 如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional |
map(Function<T, U> mapper) | 如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中 |
flatMap(Function< T,Optional<U>> 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对象
Optional<Object> optional1 = Optional.empty();
// 创建包装对象值非空的Optional对象,如果传入null则出现`NullPointerException`
Optional<String> optional2 = Optional.of("optional");
// 创建包装对象值允许为空的Optional对象
Optional<Object> optional3 = Optional.ofNullable(null);
Optional其他API
get()
作用:获取optional实例中的对象,当optional 容器为空时报错
源码:
- 判断value是否为null
- 为null,抛出 NoSuchElementException("No value present")异常
- 不为null,返回value
null值Optional:
// 创建值为null的Optional对象
Optional<String> optional = Optional.empty();
// get返回 NoSuchElementException("No value present")
String result = optional.get();
System.out.println(result);
非null值Optional:
// 创建值为:optional的Optional对象
Optional<String> optional = Optional.of("optional");
// 返回值 optional
String result = optional.get();
System.out.println(result);
isPresent()
作用:判断optional是否为空,如果空则返回false,否则返回true
源码:
代码实现:
List<String> users = new ArrayList<>();
users.add("柯南");
users.add("佩奇");
users.add("喜洋洋");
Optional<List<String>> optional = Optional.of(users);
// 判断并消费
optional.ifPresent(System.out::println);
orElse(T other)
作用:如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
源码:
代码实现:
User user = new User(1L,"格雷福斯");
// 1、存储非null数据
Optional<User> userOptional = Optional.ofNullable(user);
// 获取用户名
String name1 = userOptional.orElse(new User(0L, "帅气添甄")).getName();
// 非null,结果为:格雷福斯
System.out.println(name1);
// 2、存储null数据
Optional<User> nullOptional = Optional.ofNullable(null);
String name2 = nullOptional.orElse(new User(0L, "帅气添甄")).getName();
// 为null,结果:帅气添甄
System.out.println(name2);
orElseGet(Supplierother)
作用:如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 为null直接返回`Supplier`生产型函数接口返回的对象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 为null直接返回`Supplier`生产型函数接口返回的对象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 如果数据为null,抛出 指定异常
String name = userOptional.orElseThrow(() new RuntimeException("无数据")).getName();
System.out.println(name);
filter(Predicatep)
作用:如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 过滤名字长度大于3,如果有值才输出,没值就不输出
userOptional.filter(item -> item.getName().length() > 3).ifPresent(System.out::println);
map(Function mapper)
作用:如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中,该方法与Stream的map作用一样
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 只获取用户名
String name = userOptional.map(User::getName).orElse("添甄");
System.out.println(name);
flatMap(Function< T,Optional> mapper)
作用:在optional不为空的情况下,将对象t映射成另外一个optional,17-flatMapmap接收的是U类型,而flatMap接收的是Optional<U>类型,返回也是需要放进Optional中
源码:
代码实现:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(null);
Optional<String> optional = userOptional.flatMap(item -> Optional.ofNullable(item.getName()));
String name = optional.orElse("添甄");
System.out.println(name);
错误示范
获取用户名:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = 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<User> userOptional = Optional.ofNullable(user);
String name = userOptional.map(User::getName).orElse("无值");
System.out.println(name);
总结
- Optional是一个用来解决null值,避免发生空指针异常的容器,配合Lambda表达式写出优雅代码
- 静态工厂方法Optional.empty()、Optional.of()以及Optional.ofNullable()创建Optional对象
- Optional类包含多种方法,其中map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似
- 使用Optional能帮助你开发出更便于阅读和简介的程序
- 多使用Optional中的方法给定默认值,比如map、orElse等方法来避免过多的if判断
文章出自:石添的编程哲学,如有转载本文请联系【石添的编程哲学】今日头条号。