1. 概述

从 Java 8 开始,我们可以使用可插拔类型系统(Pluggable Type Systems)编译程序,它能提供比标准编译器更严格的类型检查。只需引入相应的注解即可。

本文将深入探讨华盛顿大学开发的 Checker Framework,这是一个强大的静态分析工具,能在编译时捕获潜在错误。

2. Maven 配置

要使用 Checker Framework,首先在 pom.xml 中添加依赖:

<dependency>
    <groupId>org.checkerframework</groupId>
    <artifactId>checker-qual</artifactId>
    <version>3.42.0</version>
</dependency>
<dependency>
    <groupId>org.checkerframework</groupId>
    <artifactId>checker</artifactId>
    <version>3.42.0</version>
</dependency>

最新版本可在 Maven Central 查询

接着配置 maven-compiler-plugin 启用类型检查:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>${maven-compiler-plugin.version}</version>
    <configuration>
        <fork>true</fork>
        <compilerArgument>-Xlint:all</compilerArgument>
        <showWarnings>true</showWarnings>
        <annotationProcessorPaths>
            <path>
                <groupId>org.checkerframework</groupId>
                <artifactId>checker</artifactId>
                <version>${checker.version}</version>
            </path>
        </annotationProcessorPaths>
        <annotationProcessors>
            <annotationProcessor>
                org.checkerframework.checker.nullness.NullnessChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.interning.InterningChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.fenum.FenumChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.formatter.FormatterChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.regex.RegexChecker
            </annotationProcessor>
        </annotationProcessors>
        <compilerArgs combine.children="append">
            <arg>-Awarns</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
            <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
            <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
        </compilerArgs>
    </configuration>
</plugin>

⚠️ 关键点:<annotationProcessors> 标签中列出了所有要启用的检查器。

3. 规避空指针异常

Checker Framework 能帮我们识别潜在的 NullPointerException 风险点:

private static int countArgs(@NonNull String[] args) {
    return args.length;
}

public static void main(@Nullable String[] args) {
    System.out.println(countArgs(args));
}

这里我们用 @NonNull 声明 countArgs() 的参数不能为 null,但在 main() 中却传入了可能为 null 的参数(@Nullable 标注)。编译时会收到警告:

[WARNING] /checker-plugin/.../NonNullExample.java:[12,38] [argument.type.incompatible]
 incompatible types in argument.
  found   : null
  required: @Initialized @NonNull String @Initialized @NonNull []

✅ 这种静态检查能提前暴露运行时才会出现的 NPE 问题。

4. 常量枚举化处理

有时我们会用一组常量模拟枚举行为。通过 @Fenum 注解可以分组管理这些常量:

static final @Fenum("country") String ITALY = "IT";
static final @Fenum("country") String US = "US";
static final @Fenum("country") String UNITED_KINGDOM = "UK";

static final @Fenum("planet") String MARS = "Mars";
static final @Fenum("planet") String EARTH = "Earth";
static final @Fenum("planet") String VENUS = "Venus";

当方法需要特定类型的常量时,可以这样声明:

void greetPlanet(@Fenum("planet") String planet){
    System.out.println("Hello " + planet);
}

如果误传了其他分组的常量:

public static void main(String[] args) {
    obj.greetPlanets(US);  // 错误:US 是 country 类型
}

Checker Framework 会立即报错:

[WARNING] /checker-plugin/.../FakeNumExample.java:[29,26] [argument.type.incompatible]
 incompatible types in argument.
  found   : @Fenum("country") String
  required: @Fenum("planet") String

5. 正则表达式验证

假设某个字符串变量需要存储包含至少一个捕获组的正则表达式:

@Regex(1) private static String FIND_NUMBERS = "\\d*";

这里明显有问题:\\d* 没有任何捕获组。编译时 Checker Framework 会警告:

[WARNING] /checker-plugin/.../RegexExample.java:[7,51] [assignment.type.incompatible]
incompatible types in assignment.
  found   : @Regex String
  required: @Regex(1) String

✅ 这种检查能避免正则表达式语法错误导致的运行时异常。

6. 总结

Checker Framework 是超越标准编译器的强大工具,能显著提升代码健壮性:

  • ✅ 在编译时捕获典型运行时错误
  • ✅ 支持自定义检查规则
  • ✅ 集成简单,Maven/Gradle 开箱即用

本文仅介绍了部分内置检查器,更多功能请查阅官方手册。完整示例代码可在 GitHub 获取。

💡 实践建议:从 NullnessChecker 开始尝试,这是最常用且效果最明显的检查器。


原始标题:The Checker Framework - Pluggable Type Systems for Java