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

大模型关系型数据库容器服务

picture.image

苏三的免费八股文网站: www.susan.net.cn

大家好,我是苏三,又跟大家见面了。

一、传统判空的血泪史

某互联网金融平台因费用计算层级的空指针异常,导致凌晨产生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用户发券  

      

  1. Optional抛出业务异常

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

      

  1. 封装通用工具类

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

      

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

  1. Spring实战技巧

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

具体代码如下:


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

      

  1. Lombok保驾护航

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

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

比如:


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

      

四、工程级解决方案

  1. 空对象模式

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

      

  1. Guava的Optional增强

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

比如:


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

      

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

五、防御式编程进阶

  1. Assert断言式拦截

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

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

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

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

例如:


        
          
public class ValidateUtils {  
    public static <T> T requireNonNull(T obj, String message) {  
        if (obj == null) {  
            throw new ServiceException(message);  
        }  
        return obj;  
    }  
}  
  
// 使用姿势  
User currentUser = ValidateUtils.requireNonNull(  
    userDao.findById(userId),   
    "用户不存在-ID:" + userId  
);  

      

  1. 全局AOP拦截

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

例如:


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

      

六、实战场景对比分析

场景1:深层次对象取值


        
          
// 旧代码(4层嵌套判断)  
if (order != null) {  
    User user = 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 (User user : 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 (user instanceof User u && u.getName() != null) {  
    System.out.println(u.getName().toUpperCase());  
}  

      

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

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

最后欢迎 加入苏三的星球 ,你将获得:苏三AI项目、 商城微服务实战、秒杀系统实战 、 商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

还有1V1答疑、修改简历、职业规划、送书活动、技术交流。

picture.image

目前星球已经更新了5200+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有1600+小伙伴加入学习。

picture.image

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
云原生机器学习系统落地和实践
机器学习在字节跳动有着丰富业务场景:推广搜、CV/NLP/Speech 等。业务规模的不断增大对机器学习系统从用户体验、训练效率、编排调度、资源利用等方面也提出了新的挑战,而 Kubernetes 云原生理念的提出正是为了应对这些挑战。本次分享将主要介绍字节跳动机器学习系统云原生化的落地和实践。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论