转载:如何编写 Null-Safety 代码?一文帮你讲透!
NullPointerExceptions(NPE)空指针异常应该是每个程序员的噩梦,作为 Java 程序员都知道:NPE 是运行时异常,在代码编译过程中很难发现这种异常。因此,这篇文章,我们将分析如何编写 null 安全代码。
在实际工作中,对于 null的处理通常有以下处理方式:
- Nullable注解
- NonNull注解
- 显示处理 null
- 使用 Optional类
- 使用断言
- 编写清晰的文档
Nullable注解
@Nullable
是 Spring 的内置注解,可用于方法、参数或字段级别,用于标记方法的返回值或参数或字段可以为 null,通过使用 @Nullable
注解,开发者可以明确地表明某个方法可能返回 null 值,从而在调用该方法时更加谨慎。
如下示例:
// 示例1:用于方法 @Nullable public String getUserById(Long userId) { if (userId == null) { return null; } // 根据userId查询用户邮箱 return userRepository.findUserById(userId); } // 示例2:用于参数 User addUser(@NonNull Long userId, @Nullable String email); // 示例3:用于字段 public class User { @NonNull Long userId; @Nullable String email; }
在上述的示例中,@Nullable
用于方法,字段,参数,标识可以为 null,因此,方法的调用用就需要根据业务判断 null。
NonNull注解
@NonNull
是 Spring 的内置注解,可用于方法、参数或字段级别,用于标记方法的返回值或参数或字段不能为 null。通过使用 @NonNull
注解,开发者可以明确地表明某个方法不会返回 null 值,从而在调用该方法时可以放心地使用返回值。
如下示例:
import org.springframework.lang.NonNull; // 示例1:用于方法 @NonNull public User getUserById(Long userId) { // 根据userId查询用户信息 User user = userRepository.findUserById(userId); if (user == null) { throw new IllegalArgumentException("User not found"); } return user; } // 示例2:用于参数 User findUserById(@NonNull Long userId); // 示例3:用于字段 public class User { @NonNull Long id; }
在上述的示例中:示例1,@NonNull
注解标记方法其返回值不能为 null,因此,方法的调用者知道该方法不会返回 null 值,可以放心地使用返回值而不用担心空指针异常。
示例2,@NonNull
注解标记方法的入参不能为 null
示例3,@NonNull
注解标记字段不能为 null,一旦字段值为 null,IDE会出现以下警告:
显式处理 null
显式处理 null应该是我们使用最多也是最灵活处理 null的方法,在编写代码时,要注意对可能为 null 的值进行显式的空值检查和处理,可以使用 if 语句、try-catch块等方式来处理空值情况,避免程序崩溃或数据丢失。
如下代码,针对 user 对象是否为 null 做不同的处理:
// 方式1 if (user != null) { // 执行操作 } else { // 处理空值情况 } // 方式2 try { // 业务处理 } catch (Exception e) { // 异常处理 }
使用 Optional类
Optional类是 Java 8 引入的,它的作用是封装一个可能为空的值,通过 Optional 类的方法,可以更加安全和优雅地处理空值情况,从而避免 NPE 空指针异常。
如下示例,针对上面手动处理 null, 我们可以使用 optional更优雅的实现 null 判断:
Optional<User> userOptional = Optional.ofNullable(user); String userName = userOptional.map(User::getName).orElse("Unknown");
关于 Optional 更多的使用,参考下图:
使用 Optional 类的几个好处:
- 可以避免 NPE 空指针异常,提高代码的健壮性;
- 可以减少手动 null 判断,使代码更简洁和优雅;
- 符合函数式编程的特性;
使用断言
断言 assert 可以帮助发现潜在的空指针异常问题,确保代码的假设得到正确验证。
如下示例:
assert user != null : "User object should not be null";
在上述示例中,我们使用断言来验证 user 对象不为 null,如果断言失败,则抛出相应的错误信息。
编写清晰的文档
在方法的注释中明确说明方法的返回值和参数是否可以为 null,以及如何处理 null情况。清晰的文档可以帮助其他开发者更好地理解代码逻辑和规范。
/** * 根据用户ID获取用户邮箱地址 * @param userId 用户ID * @return 用户邮箱地址,可能为null */ @Nullable public String getUserEmailById(Long userId) { // 方法实现 }
上述示例中,我们编写了清晰的方法注释,明确说明了方法的返回值可能为 null,帮助其他开发者更好地理解方法的使用规范和处理空值情况。
注意事项
对于上述 Spring 的 @Nullable
和 @NonNull
注解,并非所有开发工具都能显示这些编译警告,如果未看到相关警告,请检查 IDE 中的编译器设置。这里以 IntelliJ 设置为例:
为了方便 IDE 进行一些自动化代码检查,我们可以使用 SpotBugs
提供了一个 插件,可以检测由于可为 null 性而导致的代码异常。
安装上述插件后,如果任何用 @NonNull
注释的方法意外返回 null,则 SpotBugs 检查将失败,并出现类似于以下内容的错误:
[ERROR] High: xxx may return null, but is declared @Nonnull At xxx.java:[line 36] NP_NONNULL_RETURN_VIOLATION
总结
本文,我们总结了对 null处理的几种通常方式:
- Nullable注解
- NonNull注解
- 显示处理 null
- 使用 Optional类
- 使用断言
- 编写清晰的文档
功夫在平常,功夫在细节!只要我们在日常开发中多注意一点细节,让好习惯成为自然,终有一天,不但可以写出让人羡慕的优雅代码,还可以写出高质量的代码。