瞧瞧别人家的判空,那叫一个优雅!

我们在日常开发中的entity对象,一般会使用Lombok框架中的注解,来实现getter/sett
首页 新闻资讯 行业资讯 瞧瞧别人家的判空,那叫一个优雅!

一、传统判空的血泪史

某互联网金融平台因费用计算层级的空指针异常,导致凌晨产生9800笔错误交易。

DEBUG日志显示问题出现在如下代码段:

// 错误示例BigDecimal amount=user.getWallet().getBalance().add(new BigDecimal("100"));

此类链式调用若中间环节出现null值,必定导致NPE。

初级阶段开发者通常写出多层嵌套式判断:

if(user!=null){
    Wallet wallet=user.getWallet();if(wallet!=null){
        BigDecimal balance=wallet.getBalance();if(balance!=null){// 实际业务逻辑}
    }
}

这种写法既不优雅又影响代码可读性。

那么,我们该如何优化呢?

二、Java 8+时代的判空革命

Java8之后,新增了Optional类,它是用来专门判空的。

能够帮你写出更加优雅的代码。

1. Optional黄金三板斧

// 重构后的链式调用BigDecimal result=Optional.ofNullable(user).map(User::getWallet).map(Wallet::getBalance).map(balance->balance.add(new BigDecimal("100"))).orElse(BigDecimal.ZERO);

高级用法:条件过滤

Optional.ofNullable(user).filter(u->u.getVipLevel()>3).ifPresent(u->sendCoupon(u));// VIP用户发券

2. Optional抛出业务异常

BigDecimal balance=Optional.ofNullable(user).map(User::getWallet).map(Wallet::getBalance).orElseThrow(()->new BusinessException("用户钱包数据异常"));

3. 封装通用工具类

publicclass NullSafe {// 安全获取对象属性publicstatic<T,R>R get(T target,Function<T,R>mapper,R defaultValue){returntarget!=null? mapper.apply(target): defaultValue;}// 链式安全操作publicstatic<T>Texecute(T root,Consumer<T>consumer){if(root!=null){
            consumer.accept(root);}returnroot;}
}// 使用示例NullSafe.execute(user,u->{
    u.getWallet().charge(new BigDecimal("50"));logger.info("用户{}已充值",u.getId());});

三、现代化框架的判空银弹

4. Spring实战技巧

Spring中自带了一些好用的工具类,比如:CollectionUtils、StringUtils等,可以非常有效的进行判空。

具体代码如下:

// 集合判空工具List<Order>orders=getPendingOrders();if(CollectionUtils.isEmpty(orders)){returnResult.error("无待处理订单");}// 字符串检查String input=request.getParam("token");if(StringUtils.hasText(input)){
    validateToken(input);}

5. Lombok保驾护航

我们在日常开发中的entity对象,一般会使用Lombok框架中的注解,来实现getter/setter方法。

其实,这个框架中也提供了@NonNull等判空的注解。

比如:

@Getter@SetterpublicclassUser{@NonNull// 编译时生成null检查代码private String name;private Wallet wallet;}// 使用构造时自动判空Useruser=newUser(@NonNull"张三",wallet);

四、工程级解决方案

6. 空对象模式

publicinterface Notification {
    void send(String message);}// 真实实现publicclass EmailNotification implements Notification {@Overridepublicvoid send(String message){// 发送邮件逻辑}
}// 空对象实现publicclass NullNotification implements Notification {@Overridepublicvoid send(String message){// 默认处理}
}// 使用示例Notification notifier=getNotifier();notifier.send("系统提醒");// 无需判空

7. Guava的Optional增强

其实Guava工具包中,给我们提供了Optional增强的功能。

比如:

importcom.google.common.base.Optional;// 创建携带缺省值的OptionalOptional<User>userOpt=Optional.fromNullable(user).or(defaultUser);// 链式操作配合FunctionOptional<BigDecimal>amount=userOpt.transform(u->u.getWallet()).transform(w->w.getBalance());

Guava工具包中的Optional类已经封装好了,我们可以直接使用。

五、防御式编程进阶

8. Assert断言式拦截

其实有些Assert断言类中,已经做好了判空的工作,参数为空则会抛出异常。

这样我们就可以直接调用这个断言类。

例如下面的ValidateUtils类中的requireNonNull方法,由于它内容已经判空了,因此,在其他地方调用requireNonNull方法时,如果为空,则会直接抛异常。

我们在业务代码中,直接调用requireNonNull即可,不用写额外的判空逻辑。

例如:

publicclass ValidateUtils {publicstatic<T>T requireNonNull(T obj,String message){if(obj==null){
            throw new ServiceException(message);}returnobj;}
}// 使用姿势UsercurrentUser=ValidateUtils.requireNonNull(userDao.findById(userId),"用户不存在-ID:"+userId);

9. 全局AOP拦截

我们在一些特殊的业务场景种,可以通过自定义注解 + 全局AOP拦截器的方式,来实现实体或者字段的判空。

例如:

@Aspect@Componentpublicclass NullCheckAspect {@Around("@annotation(com.xxx.NullCheck)")publicObject checkNull(ProceedingJoinPoint joinPoint)throws Throwable {
        Object[]args=joinPoint.getArgs();for(Object arg : args){if(arg==null){
                throw new IllegalArgumentException("参数不可为空");}
        }returnjoinPoint.proceed();}
}// 注解使用publicvoid updateUser(@NullCheckUseruser){// 方法实现}

六、实战场景对比分析

场景1:深层次对象取值

// 旧代码(4层嵌套判断)if(order!=null){Useruser=order.getUser();if(user!=null){
        Address address=user.getAddress();if(address!=null){
            String city=address.getCity();// 使用city}
    }
}// 重构后(流畅链式)String city=Optional.ofNullable(order).map(Order::getUser).map(User::getAddress).map(Address::getCity).orElse("未知城市");

场景2:批量数据处理

List<User>users=userService.listUsers();// 传统写法(显式迭代判断)List<String>names=new ArrayList<>();for(Useruser: users){if(user!=null&&user.getName()!=null){
        names.add(user.getName());}
}// Stream优化版List<String>nameList=users.stream().filter(Objects::nonNull).map(User::getName).filter(Objects::nonNull).collect(Collectors.toList());

七、性能与安全的平衡艺术

上面介绍的这些方案都可以使用,但除了代码的可读性之外,我们还需要考虑一下性能因素。

下面列出了上面的几种在CPU消耗、内存只用和代码可读性的对比:

方案

CPU消耗

内存占用

代码可读性

适用场景

多层if嵌套



★☆☆☆☆

简单层级调用

Java Optional



★★★★☆

中等复杂度业务流

空对象模式



★★★★★

高频调用的基础服务

AOP全局拦截



★★★☆☆

接口参数非空验证

黄金法则

  • Web层入口强制参数校验

  • Service层使用Optional链式处理

  • 核心领域模型采用空对象模式

八、扩展技术

除了,上面介绍的常规判空之外,下面再给大家介绍两种扩展的技术。

Kotlin的空安全设计

虽然Java开发者无法直接使用,但可借鉴其设计哲学:

val city=order?.user?.address?.city ?:"default"

JDK 14新特性预览

// 模式匹配语法尝鲜if(userinstanceofUseru&&u.getName()!=null){
    System.out.println(u.getName().toUpperCase());}

总之,优雅判空不仅是代码之美,更是生产安全底线。

本文分享了代码判空的10种方案,希望能够帮助你编写出既优雅又健壮的Java代码。

22    2025-03-06 08:21:02    判空 entity 对象