< 返回新闻公共列表
开发小技巧系列 - 如何避免NPE,去掉if...else(四)
发布时间:2023-06-29 17:01:06
开发小技巧系列文章,是本人对过往平台系统的设计开发及踩坑的记录与总结,给初入平台系统开发的开发人员提供参考与帮助
在前面的三个章节中,对如何避免NPE,及程序中如果简化null != obj场景进行了讲解,及给出了解决方案,但回头去看写的Utils工具类,总感觉还缺少点什么,看着if ... else .... 总感觉不爽,那还有什么解决办法吗?
带着if ... else ... 的代码:
/** * 转换null值的模板方法 * @param value * 输入的值 * @param defaultValue * 默认值 * @param * 输入的对象的类型 * @return */
public static R wrapNull(R value, R defaultValue){
Optional optional = Optional.ofNullable(value);
if(optional.isPresent()){
return value;
}
return defaultValue;
}
/** * 转换输入的整数为null的情况 * @param input * 输入数值 * @param defaultValue * 指定默认值 * @return * 如果是null,则返回 defaultValue */
public static Integer nullInt(Integer input, Integer defaultValue){
Optional optional = Optional.ofNullable(input);
if(optional.isPresent()){
return input;
}
return defaultValue;
}
带着这个疑问,打开了Optional的源代码。
package java.util;
....
public final class Optional {
...
public static Optional empty() {
@SuppressWarnings("unchecked")
Optional t = (Optional) EMPTY;
return t;
}
/** * 返回一个值不为Null的 {@code Optional} 对象 . * * @param 对象的class * @param 非空的对象(值) * @return an {@code Optional} with the value present * @throws 如果值为NULL, 会抛出NullPointerException */
public static Optional of(T value) {
return new Optional<>(value);
} /** * 返回一个输入对象(值)的 {@code Optional} 对象(值), * 如果值为空,则返回一个 empty {@code Optional}. * * @param 对象(值)的class * @param 输入的值(对象) * @return 非空则返一个Optional的对象(值),传入的值为null,则返回一个EMPTY对象 */
public static Optional ofNullable(T value) {
return value == null ? empty() : of(value);
}
//在往下看,还可以发现他还支持map, flatmap, orElse等方法
public Optional map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
//值转化函数
public Optional flatMap(Function super T, Optional> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
} /** * 返回当前Optional中的值(对角) * 如果不存在,则返回 {@code other}. * * @param other 当前Optional的值不存在时,需要返回的值(一般用于默认赋值的场景) * @return 略 */
public T orElse(T other) {
return value != null ? value : other;
}
...省略
}
看到这里,在回头看原来写的if ... else ..., 原来还可以这样,在看修改的代码前,先来看下Optional的这几个api的说明与用途。
从Optional的源码上看,Optional是一个final类,不可能在被继承,而且它的构造函数是private(不可见的),不能直接通过new XXX(...)这样来创建一个新的对象。但它提供了3个public的静态构造方法,用来构造一个带真实值的Optional对象,和一个代表空值的对象EMPTY。即,of(T value)、ofNullable(T value)、 empty(),在这向个函数的内部都是调用内部的构造方法。
of(T value) : 传入的 value 值或对角不能为Null,如果为Null,则会抛出NullPointerException的异常。ofNullable(T value) : 可以传入任意的值或对象,包括Null,都不会抛出NullPointerException的异常。empty() :构造一个 value = null的空对象。
在往下看,可以看到它还有其他的api方法,如fliter(...)、map(...)、flatMap(...)、orElse(...)、orElseGet(...)、orElseThrow(...)等,从这些方法的定义上看,都是支持JDK1.8中的lamda表达式的语法。
数据转换:map(...), flatMap(...)分支:orElse(...)、orElseGet(...)、orElseThrow(...)过滤:filter(...),返回满足条件的数值,如果不满足,会返回empty
好了,通过对api的学习,原来还可以这样简单(见ValueUtils.java, ValueUtilsV2.java),像
/** * 转换null值的模板方法 * @param value * 输入的值 * @param defaultValue * 默认值 * @param * 输入的对象的类型 * @return */
public static R wrapNull(R value, R defaultValue){
Optional optional = Optional.ofNullable(value);
if(optional.isPresent()){
return value;
}
return defaultValue;
}
可以修改为
/** * 转换null值的模板方法 * @param value * 输入的值 * @param defaultValue * 默认值 * @param * 输入的对象的类型 * @return */
public static R wrapNull(R value, R defaultValue){
return Optional.ofNullable(value).orElse(defaultValue);
}
像这样的
/** * 转换BigDecimal(浮点数)数值可能为null的情况 * @param value * 输入数值 * @param scale * 指定小数位 * @param defaultValue * 指定默认值 * @return * 默认四舍五入 * 如果为null,则返回 defaultValue */
public static BigDecimal wrapNull(BigDecimal value, int scale, BigDecimal defaultValue){
Optional optional = Optional.ofNullable(value);
if(optional.isPresent()){
return value.setScale(scale, BigDecimal.ROUND_HALF_UP);
}
return defaultValue;
}
可以改成
/** * 转换BigDecimal(浮点数)数值可能为null的情况 * @param value * 输入数值 * @param scale * 指定小数位 * @param defaultValue * 指定默认值 * @return * 默认四舍五入 * 如果为null,则返回 defaultValue */
public static BigDecimal wrapNull(BigDecimal value, int scale, BigDecimal defaultValue){
return Optional.ofNullable(value).map(e->e.setScale(scale, BigDecimal.ROUND_HALF_UP)) .orElse(defaultValue);
}
OptionalUtils.java中的修改:
/** * 属性互转模板方法 * @param source * 原对象(需要比较的对象) * @param function * 对象E->R的赋值过程 * @param defaultObject * 默认值 * @param * 原始对象 * @param * 结果对象 * @return */
public static R transfer(E source, Function function, R defaultObject){
//旧的逻辑处理逻辑
//Optional optional = Optional.ofNullable(source);
//if(optional.isPresent()){
//return function.apply(optional.get());
//}
//return defaultObject;
//利用orElse后的处理逻辑
return Optional.ofNullable(source).map(e->function.apply(e)).orElse(defaultObject);
}
代码调整完了,看看单元测试的结果
/** * 测试正常情况下的调用(非null) */
@Test
public void optionalTest(){
// 声明一个会员
Member member = new Member();
member.setId(1);
member.setMemberId(1000);
member.setNickName("测试");
member.setGender(1); MemberDTO memberDTO = memberService.transferByOptional(member, null);
log.debug("{}", memberDTO);
//声明一个部门
Dept dept = new Dept();
dept.setId(1);
dept.setDeptId(100);
dept.setParentId(0);
dept.setDeptName("部门");
DeptDTO deptDTO = OptionalUtils.transfer(dept, MemberService::cover, null);
log.debug("deptDTO : {}", deptDTO);
}
17:03:16.645 [main] DEBUG net.jhelp.demo.OptionalTest - MemberDTO(memberId=1000, nickName=测试, realName=null, gender=1, genderName=男)
17:03:16.674 [main] DEBUG net.jhelp.demo.OptionalTest - deptDTO : DeptDTO(deptId=100, parentId=0, deptName=部门)
/** * 测试传入对象是null的情况 */
@Test
public void optionalWithNullTest2(){
MemberDTO2 memberDTO = memberService.transferByOptional2(null, MemberDTO2.EMPTY_MEMBER);
log.debug("optionalWithNullTest2:{}", memberDTO);
log.debug("是否是空对象:{}", memberDTO.isEmpty());
}
17:04:42.572 [main] DEBUG net.jhelp.demo.OptionalTest - optionalWithNullTest2:MemberDTO2(memberId=null, nickName=null, realName=null, gender=null, genderName=null)
17:04:42.583 [main] DEBUG net.jhelp.demo.OptionalTest - 是否是空对象:true
具体的代码调整,可以下载测试的demo程序。
https://gitee.com/TianXiaoSe_admin/java-npe-demo.git
总结一下
由于Optional中加了lamda的新特性,代码是简化,但可能不易理解,还是要根据实际的需要来使用。
思考、偿试比结论重要,希望你能从中有所收获。
开发小技巧系列文章:
1、开发小技巧系列 - 库存超卖,库存扣成负数?2、开发小技巧系列 - 重复生成订单3、开发小技巧系统 - Java实现树形结构的方式有那些?4、开发小技巧系列 - 如何避免NullPointerException?(一)5、开发小技巧系列 - 如何避免NullPointerException?(二)6.开发小技巧系列 - 如何避免NPE,巧用Optional重构三元表达式?(三)