你真的处理好 null 了吗?——11种常见但容易被忽视的空值处理方式

你真的处理好 null 了吗?——11种常见但容易被忽视的空值处理方式

引言

null,这个看似简单的词,成就了无数的 NullPointerException。

在 Java 这个广阔的世界里,null 永远是个绕不开的存在和话题。

从最早的手动判空,到 Optional,再到各种注解、工具库......我们在写代码时几乎每天都要和它battle。

但你真的处理好 null 了吗?

今天我们不去长篇大论地讲空指针异常的历史(埋个坑,我们以后可以聊聊),也不做泛泛而谈。

只结合自己这些年写项目时的一点小心得,呈现几种最常见、但也最容易被忽视的空值处理方式。

不吹不黑,建议你耐心看完,一定会有所收获。

正文

基础操作:你都掌握了吗?

1. 传统判空:告别 if 地狱

反面教材 ❌:

java

复制代码

if (user != null && user.getName() != null && user.getName().length() > 0) {

System.out.println("用户名:" + user.getName());

}

乍一看没问题,但:

链式访问容易漏判;

代码臃肿、难读;

多层嵌套,越写越像 if 地狱;

推荐写法 ✅:

java

复制代码

// 方式1:卫语句 - 提前返回

if (user == null || user.getName() == null || user.getName().isEmpty()) {

System.out.println("用户名:匿名用户");

return;

}

System.out.println("用户名:" + user.getName());

// 方式2:三元运算符 - 简化逻辑

String displayName = (user != null && user.getName() != null && !user.getName().isEmpty())

? user.getName()

: "匿名用户";

System.out.println("用户名:" + displayName);

虽然代码会多几行,但是这样处理的好处很明显:

卫语句能减少嵌套层级,让主逻辑更清晰;

三元运算符适合简单的空值替换场景;

最终目的是使代码更易读,逻辑分离明确;

2. 工具类大合集:让判空逐渐优雅

Apache Commons、Hutool、SpringUtils... 各种第三方的判空工具,掌握这些让你事半功倍。

这里举点例子:

字符串判空:

java

复制代码

// ❌ 原始写法

if (str != null && !str.trim().equals("")) { ... }

// ✅ Apache Commons

if (StringUtils.isNotBlank(str)) { ... }

// ✅ Hutool

if (StrUtil.isNotBlank(str)) { ... }

集合判空:

java

复制代码

// ❌ 原始写法

if (list != null && !list.isEmpty()) { ... }

// ✅ Spring Utils

if (!CollectionUtils.isEmpty(list)) { ... }

// ✅ Apache Commons

if (CollectionUtils.isNotEmpty(list)) { ... }

对象判空:

java

复制代码

// ❌ 原始写法

if (obj != null) { ... }

// ✅ Spring Utils

if (!ObjectUtils.isEmpty(obj)) { ... }

// ✅ Hutool

if (ObjUtil.isNotNull(obj)) { ... }

统一风格 + 提高可读性,选择一套坚持用下去就对了。

3. Optional 进阶:不仅判空,还能流式处理

相信 Optional 的基础用法大家都会,但是一些高级操作你都用过吗?

比如下面这些:

java

复制代码

// 链式取值

String city = Optional.ofNullable(user)

.map(User::getProfile)

.map(Profile::getAddress)

.map(Address::getCity)

.orElse("未知城市");

// 条件过滤

Optional activeUser = Optional.ofNullable(user)

.filter(u -> u.isActive())

.filter(u -> u.getAge() > 18);

// 存在即执行

Optional.ofNullable(user)

.ifPresent(u -> sendWelcomeEmail(u.getEmail()));

注意:

Optional 不推荐用作 方法参数;

也不建议用于 类字段;

它可以用于返回值,用来明确告知调用方------该接口返回值可能缺失;

4. 参数校验:在入口拦截 null

最大的问题是,最开始把所有 null 都放进来了,后面只好到处加 if 去补救。

但成熟的代码应该在最外层就把 null 拦住,而不是留给内部逻辑去兜底。

比如我们接触最多的Spring Boot 项目:

java

复制代码

@PostMapping("/create")

public void createUser(@Valid @RequestBody UserDTO user) {

// 这里的 user 已经通过校验,字段不会为 null

userService.save(user); // 无需再判空

}

// DTO 中的校验注解

public class UserDTO {

@NotNull(message = "用户名不能为空")

@NotBlank(message = "用户名不能为空字符串")

private String name;

@NotNull(message = "邮箱不能为空")

@Email(message = "邮箱格式不正确")

private String email;

}

如果是普通方法调用:

java

复制代码

public void createUser(UserDTO user) {

// 方法入口立即校验

Objects.requireNonNull(user, "用户信息不能为空");

Objects.requireNonNull(user.getName(), "用户名不能为空");

Objects.requireNonNull(user.getEmail(), "邮箱不能为空");

// 后续逻辑无需判空

userService.save(user);

}

核心思想:

Web 层用 @Valid 注解校验;

Service 层用 Objects.requireNonNull() 校验;

其他 Web 框架(如 Solon、Quarkus)也有类似的参数校验机制;

在入口处拦截,让内部逻辑专注于业务,减少心智负担,同时也避免业务代码更纯粹,不用被判空的逻辑污染。

5. 永远不要返回 null 集合

反面教材 ❌:

java

复制代码

public List getOrders(String userId) {

if (noData) return null; // 猜猜后面会发生什么

return orderList;

}

// 调用方必须判空,否则...

List orders = getOrders("123");

for (Order order : orders) { // 触发 NullPointerException

System.out.println(order.getId());

}

为什么不建议返回 null 集合?

调用方容易忘记判空:大多数人习惯直接遍历集合,很少会去想集合本身可能是 null;

增加心智负担:每次调用都得想一下这个方法会不会返回 null;

代码冗余 :避免到处都是 if (list != null) 的判断;

违反直觉:集合的语义其实就是容器,空容器比 null 更符合预期;

推荐写法 ✅:

java

复制代码

public List getOrders(String userId) {

return Optional.ofNullable(orderList)

.orElse(Collections.emptyList());

}

// 调用方可以安心遍历

List orders = getOrders("123");

for (Order order : orders) { // 相当安全!即使没数据也不会报错

System.out.println(order.getId());

}

更好的做法:

DAO 层、Service 层统一约定:永远不返回 null 集合或数组;

返回 Collections.emptyList() 或 new ArrayList<>();

调用方就能安心遍历、安心调用 size()、isEmpty() 等方法;

看腻了?上干货:高效但少有人知

6. Objects.requireNonNull() 的隐藏用法

大多数人只知道它用来校验:

java

复制代码

this.name = Objects.requireNonNull(name, "name cannot be null");

但它还有个神奇的返回值特性,配合IDE有神奇的功效:

java

复制代码

// 一行代码既校验又赋值

public void setUser(User user) {

this.user = Objects.requireNonNull(user, "User cannot be null");

// user 在这里永远不会是 null,IDE 也能识别出来

}

// 在流式处理中当过滤器

users.stream()

.map(u -> Objects.requireNonNull(u.getEmail(), "Email missing"))

.collect(toList());

妙处就在于 IDE 的静态分析器会识别这个方法,后续代码会自动帮我们检查,不再有 空值警告。

7. 接口默认方法 + null 处理

Java 8+ 的接口默认方法可以做很多神奇的事:

java

复制代码

interface UserService {

// 原始方法,可能返回 null

User findUserById(String id);

// 额外提供一个安全版本

default Optional findUserSafely(String id) {

return Optional.ofNullable(findUserById(id));

}

// 额外提供另一个带默认值的版本

default User findUserOrDefault(String id) {

return Optional.ofNullable(findUserById(id))

.orElse(User.anonymous());

}

}

这样写, 调用方有三种选择,不仅三种方法能够见名知意,向后兼容还更安全,调用方只会夸我们 🐂🍺。

8. Record + 构造时校验

Java 14+ 的 Record 配合构造器校验:

java

复制代码

public record User(String name, String email) {

// 紧凑构造器,自动校验

public User {

name = Objects.requireNonNull(name, "Name cannot be null");

email = Objects.requireNonNull(email, "Email cannot be null");

}

// 静态工厂方法处理可选字段

public static User createOptional(String name, String email) {

return new User(

Optional.ofNullable(name).orElse("Anonymous"),

Optional.ofNullable(email).orElse("unknown@example.com")

);

}

}

Record 天然不可变,配合构造时校验,直接从根源上消除 null 传播,把NPE扼杀在摇篮中。

9. @Nullable / @NonNull 注解 + IDE 静态检查

虽然是注解,但这是 编译时 的 null 处理,能在写代码时就发现潜在的空指针问题。

先看基础用法:

java

复制代码

public class UserService {

// 明确声明可能返回 null

public @Nullable User findUser(String id) {

return repository.findById(id);

}

// 明确声明参数不能为 null

public void saveUser(@NonNull User user) {

repository.save(user);

}

// 字段也可以标注

@NonNull private String serviceName = "UserService";

@Nullable private User cachedUser;

}

IDE 智能提示在后续调用时就会有特殊的功效了:

java

复制代码

// 1. 调用可能返回 null 的方法

User user = userService.findUser("123");

user.getName(); // IDE 黄色警告:Possible NPE!

// 2. 传入 null 参数

userService.saveUser(null); // IDE 警告:Parameter should not be null

// 3. 正确的处理方式

User user = userService.findUser("123");

if (user != null) {

user.getName(); // ✅ IDE 不再警告,智能推断非空

}

配置 IDE 检查级别:

IntelliJ IDEA:Settings → Inspections → Probable bugs → Nullability problems

可以设置为 Error 级别,把警告升级为编译错误

支持整个项目的批量检查

这类的注解有好几种选择,一般主流注解有这些:

java

复制代码

// 1. JetBrains 注解 (推荐,IDE 原生支持)

import org.jetbrains.annotations.NotNull;

import org.jetbrains.annotations.Nullable;

// 2. JSR-305 (Google、Spring 都在用)

import javax.annotation.Nonnull;

import javax.annotation.Nullable;

// 3. Spring 框架注解

import org.springframework.lang.NonNull;

import org.springframework.lang.Nullable;

除了上面这两种注解,还有一个更加进阶的技巧,相信很多人都没有见过:

java

复制代码

// 契约式编程:方法级别的空值约定

@Contract("null -> null; !null -> !null")

public @Nullable String processName(@Nullable String input) {

return input == null ? null : input.trim();

}

// IDE 能理解这个契约,智能推断返回值

String name = processName(user.getName());

// 如果 user.getName() 确定非空,IDE 就知道 name 也非空

结合这些注解,让我们能在写代码时就及时发现空指针的异常,非常高效。

10. Stream 的隐式 null 过滤

Stream 有个不太为人知的特性:

java

复制代码

List names = Arrays.asList("Alice", null, "Bob", null, "Charlie");

// 隐式过滤 null

List validNames = names.stream()

.filter(Objects::nonNull) // 过滤 null

.map(String::toUpperCase)

.collect(toList());

// 更神奇的:flatMap 自动跳过 null Optional

List> optionals = Arrays.asList(

Optional.of("Alice"),

Optional.empty(),

Optional.of("Bob")

);

List result = optionals.stream()

.flatMap(Optional::stream) // 空的 Optional 被自动跳过

.collect(toList());

大招------你绝对想不到

说了这么多 Java 的 null 处理技巧,但说实话------你累不累?

我是挺累的。

看看 Kotlin 是怎么做的:

kotlin

复制代码

// ❌ Java 写法:一堆判空逻辑

Optional.ofNullable(user)

.map(User::getProfile)

.map(Profile::getAddress)

.map(Address::getCity)

.orElse("Unknown");

// Kotlin 写法:安全调用操作符

val city = user?.profile?.address?.city ?: "Unknown"

一行代码解决所有问题!

更牛的是,Kotlin 在类型系统层面就区分了可空和非空:

kotlin

复制代码

var name: String = "Alice" // 非空类型,编译器保证不会是 null

var email: String? = null // 可空类型,必须显式处理

name = null // ❌ 编译错误!

email?.length // ✅ 安全调用,null 时返回 null

email!!.length // ⚠️ 强制解包,确定非空时使用

类型系统直接告诉你哪里可能有 null,哪里绝对没有 null------这不比写一堆 Optional.ofNullable() 香吗?😏

而且,Kotlin 100% 兼容 Java,基本上可以渐进式迁移。

体验过 ?. 和 ?: 的丝滑后,谁还想再去写那又臭又长的裹脚布?

有时候,解决问题的最好方式,是解决掉提出问题的人。 🤣

写在最后

对于代码来说,null 本身不是错误,它只是一个状态的表达。

主要是我们对它缺乏约定、缺乏相应的有效的应对机制,所以才导致满世界的NPE。

其实最终还是看我们是否有意识地去应对它,并统一团队的写法习惯。

代码之道,始于判空,终于优雅。

相关科技文章

美竹铃最新番号大全
365bet官方网站是多少

美竹铃最新番号大全

⌚ 08-09 👁️ 8061
印刷厂自动拼版软件(印刷拼板软件)
Bet体育365提款不到账

印刷厂自动拼版软件(印刷拼板软件)

⌚ 09-07 👁️ 9916
北京党员教育
Bet体育365提款不到账

北京党员教育

⌚ 07-11 👁️ 6029
人人网(人人)
365bet官方网站是多少

人人网(人人)

⌚ 07-17 👁️ 793
如何修复模糊的 iPhone 主屏幕:有效的解决方案
Bet体育365提款不到账

如何修复模糊的 iPhone 主屏幕:有效的解决方案

⌚ 07-08 👁️ 1554

合作伙伴