Java JSpecify 空安全注解使用指南

本文介绍如何使用 JSpecify 空安全注解

前言

在 Java 开发中,NullPointerException(空指针异常)是所有开发者的“常客”,也是业界公认的“十亿级 BUG”。 Java 原生语法没有空安全机制, 编译器无法识别空值风险,所有空指针问题只能在运行时暴露,极大增加了调试成本与线上故障风险。

以往我们依赖 @Nonnull@Nullable 等第三方注解(JetBrains、Spring)做空值标记,但各类注解规范不统一、兼容性差、无官方标准,导致静态分析工具识别混乱。

JSpecify 是一套 Java 空值注解规范,由 Google、Oracle、JetBrains 等大厂共同推动。 它提供了一套统一、简洁、可跨工具兼容的空值注解体系,配合 IDE 静态检查、编译器校验,从编码阶段杜绝大部分 NPE 风险。 目前 Spring Framework 7、Spring Boot 4、JUnit6 等主流框架已经默认携带 JSpecify 注解。

引入 JSpecify 依赖

Maven

1
2
3
4
5
6
<dependency>
    <groupId>org.jspecify</groupId>
    <artifactId>jspecify</artifactId>
    <version>1.0.0</version>
    <scope>compile</scope>
</dependency>

Gradle

1
implementation 'org.jspecify:jspecify:1.0.0'

提示:Spring Boot 4 已内置集成 JSpecify,无需手动引入依赖,可直接使用。

JSpecify 核心注解

JSpecify 核心仅 4 个注解,分为类型标记注解作用域全局注解两类,覆盖所有空值场景。

@Nullable

作用:标记当前类型、字段、参数、返回值允许为 null,是开发中最常用的注解。

适用场景:可选参数、可能为空的查询返回值、非必填字段。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.jspecify.annotations.Nullable;

public class User {
    // 非必填字段,允许为null
    @Nullable
    private String avatarUrl;

    // 可选参数,支持传入null
    public void setRemark(@Nullable String remark) {
        this.remark = remark;
    }

    // 查询结果可能为空,返回值标记可空
    @Nullable
    public User findById(Long id) {
        return id == null ? null : userMapper.selectById(id);
    }
}

@Nonnull

作用:标记当前类型绝对不能为 null,编译器与 IDE 会强制校验空值赋值、传参风险。

注意:配合 @NullMarked 全局注解时,绝大多数场景无需手动添加,仅用于局部特殊强校验场景。

示例:

1
2
3
4
5
6
import org.jspecify.annotations.NonNull;

// 强制用户名参数非空,传入null直接触发静态校验警告
public void setUsername(@NonNull String username) {
    this.username = username;
}

@NullMarked

核心作用:类/包级全局生效,标记范围内所有未显示注解的类型,默认全部非空。仅需要对可空场景手动添加 @Nullable,极大减少注解冗余。

生效范围:可标记在类、接口、package-info.java

示例 1 (类级全局非空):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

// 当前类所有字段、参数、返回值默认非空
@NullMarked
public class OrderService {

    // 默认非空,无需加 @NonNull
    public String getOrderNo(Long orderId) {
        return "ORD_" + orderId;
    }

    // 仅特殊可空场景手动标记
    @Nullable
    public String getDiscount() {
        return isVip() ? null : "95折";
    }
}

示例 2 (包级全局非空) (推荐):

1
2
3
4
@NullMarked
package com.example.service;

import org.jspecify.annotations.NullMarked;

该包下所有类自动开启默认非空,无需逐个类标记。

@NullUnmarked

作用:抵消上级 @NullMarked 的全局效果,当前类/包恢复空值未定义的原生状态,适用于新旧代码过渡场景。

使用场景:全局包开启 @NullMarked 后,部分老旧工具类、兼容类需要保留原生空值逻辑,可单独标记取消全局约束。

示例:

1
2
3
4
5
6
7
8
// 取消全局非空规则,当前类恢复原生空值不确定状态
@NullUnmarked
public class OldUtils {
    // 无默认非空约束,可自由赋值 null
    public static String getConfig(String key) {
        return null;
    }
}

特殊场景用法:数组、泛型、可变参数

JSpecify 支持精细化处理复杂类型的空值校验,解决传统注解无法覆盖的场景。

数组空值标记

  • String @Nullable []:数组对象可空,数组元素非空
  • @Nullable String []:数组对象非空,数组元素可空
  • String @Nullable [] @Nullable []:二维数组精细化空值控制
1
2
3
4
5
// 数组可空,元素非空
public void printArray(String @Nullable [] array) {}

// 数组非空,元素可空
public void handleData(@Nullable String[] data) {}

可变参数

1
2
// 可变参数元素可空
public void batchHandle(@Nullable String... args) {}

泛型空值约束

1
2
3
4
// 泛型参数默认非空,返回值可空
public <T> @Nullable T getOrDefault(@NonNull T value) {
    return value == null ? null : value;
}

总结

JSpecify 核心价值是为 Java 建立统一、标准、简洁的空安全编码规范,核心作用就是将 NPE 从“运行时问题”前移到“编译期问题”。

如果本文对您有所帮助,欢迎打赏支持作者!

Licensed under CC BY-NC-SA 4.0
最后更新于 2026-08-17 14:38
使用 Hugo 构建
主题 StackJimmy 设计