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

内容分享5小时前发布
0 18 0

一、传统判空的血泪史

某互联网金融平台因费用计算层级的空指针异常,导致凌晨产生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. 封装通用工具类

public class NullSafe {
    
    // 安全获取对象属性
    public static <T, R> R get(T target, Function<TRmapperR 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());
});

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

4. 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); 
}

5. 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);

四、工程级解决方案

6. 空对象模式

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("系统提醒"); // 无需判空

7. 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类已经封装好了,我们可以直接使用。

五、防御式编程进阶

8. Assert断言式拦截

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

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

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

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

例如:

public class ValidateUtils {
    public static <T> requireNonNull(T obj, String message) {
        if (obj == null) {
            throw new ServiceException(message);
        }
        return obj;
    }
}

// 使用姿势
User currentUser = ValidateUtils.requireNonNull(
    userDao.findById(userId), 
    "用户不存在-ID:" + userId
);

最近就业形势比较困难,为了感谢各位小伙伴对苏三一直以来的支持,我特地创建了一些工作内推群, 看看能不能协助到大家。

你可以在群里发布招聘信息,也可以内推工作,也可以在群里投递简历找工作,也可以在群里交流面试或者工作的话题。

添加苏三的私人微信:li_su223,备注:掘金+所在城市,即可加入。

9. 全局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代码。

© 版权声明

相关文章

18 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    文娱 读者

    自认为的优雅。平白无故的去创建根本无用的Optional对象。这不是偶尔创建,而是每次这么消耗。java再怎么性能强劲都被这自认为的优雅消耗了。

    无记录
  • 头像
    悟空的指环 读者

    最讨厌这玩意了,这if esle不好吗

    无记录
  • 头像
    月落伤旋 读者

    优雅是指简洁有力量oop到此为止了

    无记录
  • 头像
    念念SAMA_ 投稿者

    一直理解不了为啥要有空判断,多麻烦都是些这玩意

    无记录
  • 头像
    Blue昼漾 投稿者

    最好的还是JS的?. 和??

    无记录
  • 头像
    近似数 读者

    语法糖而已

    无记录
  • 头像
    kaylajade 读者

    Optional类,它是用来专门判空的。为什么而不能直接is Null判断

    无记录
  • 头像
    醉梦书生 读者

    无论如何,或许其他全部是假。但个人与集团利益是真!

    无记录
  • 头像
    仃已 读者

    Optinal库

    无记录
  • 头像
    槐序 读者

    这就是java的量子性,每个人都不知道自已收到了什么,也不知道自已给出了什么。

    无记录
  • 头像
    醉诗吟 读者

    Rust 轻松解决

    无记录
  • 头像
    仙儿 读者

    花里胡哨

    无记录
  • 头像
    鑫极鑫极 读者

    真麻烦!

    无记录
  • 头像
    likool 投稿者

    优雅? 你自认为的吧 我到觉得第一个判空逻辑才优雅 又直观 又复合机器逻辑

    无记录
  • 头像
    飛鳥與魚 读者

    就是帮你做了一些你平时做的事,你感谢他个屁

    无记录
  • 头像
    别管小文咯 投稿者

    还是一如既往的又臭又长

    无记录
  • 头像
    蓝色印迹 读者

    不够优雅是真的,说影响可读性,纯属扯淡

    无记录
  • 头像
    先力 读者

    渣哇

    无记录