- 静态分析
- 代码质量
- 开发工具
【免费下载链接】error-prone
Catch common Java mistakes as compile-time errors
导读
本文介绍 Error Prone(error-prone)内置检查器TryWithResourcesVariable:当代码把资源先赋值给一个final或"有效 final"(effectively-final)变量,再在try-with-resources中用另一个变量去包装它时,该检查器会提示这层包装是多余的,并自动将其重构为 Java 9 引入的"资源引用变量"写法。读完本文,你将掌握该检查器的匹配规则、自动修复行为、启用/抑制方式,以及它在当前仓库中的完整源码实现与测试证据。
Java 9 带来的写法变化:资源可以是变量引用
从 Java 9 开始,try-with-resources语句中的资源不再必须是"声明式资源"(即紧跟类型的变量声明),也可以是对一个final或"有效 final"变量的引用。这意味着:
AutoCloseable resource = ...; try (resource) { // Java 9+ 合法写法 doSomething(resource); }在 Java 9 之前,上面的写法是不合法的,开发者被迫多写一层"搬运"变量:
AutoCloseable resource = ...; try (AutoCloseable resource2 = resource) { // 多余的包装变量 doSomething(resource2); }第二种写法不仅在语法上多出一个变量,还容易造成命名混乱——resource2与resource指向同一个对象,却被当作两个不同的资源对待。TryWithResourcesVariable检查器的作用,正是发现这类多余的包装变量,并把它改写成第一种更简洁的写法。
需要特别强调的是,该检查器并不改变变量的作用域语义:被引用的变量在try块执行完毕后依然存在,只是不再拥有资源生命周期;资源的关闭仍然由try-with-resources语句负责。
检查器能发现什么:匹配规则与边界
根据关联文档 TryWithResourcesVariable.md 的说明,该检查器只针对一种非常具体的模式:try的资源列表里出现Type x = identifier;形式的变量声明,且初始化的identifier是一个final或"有效 final"的变量。
文档同时给出了一个重要边界:
资源不能是任意表达式,例如
try (returnsTheResources()) { ... }这种写法仍然是不允许的。
也就是说,检查器不会(也无法)把任意表达式改写成资源引用——资源引用必须是某个已存在的、可被 Java 语义判定为 final/有效 final 的变量,而不是方法调用、字段访问或其他表达式的结果。这也是它只匹配IdentifierTree(标识符)而不匹配其他表达式的原因。
源码实现剖析:matchTry 的四步判定
检查器主体位于 TryWithResourcesVariable.java。它实现TryTreeMatcher,核心逻辑在matchTry方法中,对每个try语句做如下四步判定(对应 TryWithResourcesVariable.java#L45-L67):
- 遍历资源列表:
for (Tree resource : tree.getResources()),逐一检查try (...)括号里的每个资源。 - 必须是变量声明:资源必须是
VariableTree(即Type name = ...的声明形式);如果已经是try (resource)这种纯引用形式,则跳过。 - 初始化器必须是标识符:
initializer instanceof IdentifierTree,排除方法调用、成员访问、字面量等一切其他表达式。 - 符号必须被视为 final:调用
ASTHelpers.isConsideredFinal(getSymbol(initializer))确认被引用变量是final或"有效 final"。
第四步判定的底层实现在 ASTHelpers.java 的isConsideredFinal方法(ASTHelpers.java#L1917-L1920):
/** Returns whether {@code symbol} is final or effectively final. */ public static boolean isConsideredFinal(Symbol symbol) { return (symbol.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0; }该方法同时检查Flags.FINAL(显式final)与Flags.EFFECTIVELY_FINAL(编译器推导出的"有效 final",即变量只被赋值一次)两个标志位。因此,即使变量没有写final关键字,只要它从未被重新赋值,检查器同样会认为它符合条件。
检查器自身的@BugPattern注解(TryWithResourcesVariable.java#L38-L42)声明如下:
@BugPattern( summary = "This variable is unnecessary, the try-with-resources resource can be a reference to a" + " final or effectively final variable", severity = WARNING)诊断级别为WARNING,触发时会给出提示:该变量是多余的,try-with-resources的资源可以直接引用这个 final/有效 final 变量。
自动修复:替换声明并重命名所有引用
检查器并非只报告问题,它还会生成一份可直接应用的SuggestedFix自动修复,由两部分合并而成(TryWithResourcesVariable.java#L61-L64):
SuggestedFix.builder() .replace(getStartPosition(variableTree), state.getEndPosition(initializer), name) .merge(SuggestedFixes.renameVariableUsages(variableTree, name, state)) .build()).replace(...):把Type name = initializer整段声明替换为被引用变量的名字name,即try (AutoCloseable r2 = r1)→try (r1)。.merge(SuggestedFixes.renameVariableUsages(...)):把原包装变量在try块内部的所有使用处一并重命名为被引用变量,例如System.err.println(r2)→System.err.println(r1),从而消除对已消失包装变量的悬空引用。
因此,开发者只需要在编译产物中应用 Error Prone 给出的修复,或在 IDE 中接受建议,就能一键完成重构。
测试验证:三种典型场景
当前仓库的测试位于 TryWithResourcesVariableTest.java,使用BugCheckerRefactoringTestHelper做输入/输出对比测试,覆盖三个典型场景:
单变量重构(TryWithResourcesVariableTest.java#L30-L58):
// 输入 void f(AutoCloseable r1) { try (AutoCloseable r2 = r1) { System.err.println(r2); } catch (Exception e) {} } // 输出 void f(AutoCloseable r1) { try (r1) { System.err.println(r1); } catch (Exception e) {} }多变量同时重构(TryWithResourcesVariableTest.java#L60-L92):
try (AutoCloseable b1 = a1; AutoCloseable b2 = a2)会被同时改写为try (a1; a2),说明检查器对资源列表中的每个资源独立判定。非 final 变量保持不变(TryWithResourcesVariableTest.java#L94-L114):当被引用的变量
r1在进入try之前被重新赋值(r1 = reassign(r1);),它不再是"有效 final",测试断言expectUnchanged()——检查器不产生任何修改。这正对应了第 4 步判定中isConsideredFinal返回false的情况。
启用方式:默认关闭,按需打开
需要特别留意的是,该检查器在 Error Prone 中默认不启用。查看内置检查器注册表 BuiltInCheckerSuppliers.java,TryWithResourcesVariable.class被列在DISABLED_CHECKS(默认关闭的检查列表)中(BuiltInCheckerSuppliers.java#L1237-L1362)。这类检查通常属于代码风格优化(Style/Simplification 范畴),不影响运行时正确性,因此默认不打扰开发者。
按 Error Prone 的命令行规则,可以通过编译器的-Xep系列参数启用它:
# 以默认级别(WARNING)启用 -Xep:TryWithResourcesVariable # 显式指定级别启用 -Xep:TryWithResourcesVariable:WARN # 配合全局关闭再选择性开启(例如仅允许该检查) -XepDisableAllChecks -Xep:TryWithResourcesVariable:WARN在 Maven 等构建工具中,可将上述参数配置到maven-compiler-plugin的compilerArgs中;使用 Bazel 构建时,则通过--jvmopt或javacopts传入对应的-Xep参数。
抑制方式
如果某个位置确实想保留包装变量(例如刻意缩小资源引用的作用域),可以在类、方法或变量声明上使用@SuppressWarnings("TryWithResourcesVariable")来抑制该检查,检查名取自@BugPattern注解(未显式指定name时即用类名):
@SuppressWarnings("TryWithResourcesVariable") void keepWrapper(AutoCloseable r1) { try (AutoCloseable r2 = r1) { ... } }总结
TryWithResourcesVariable是一个小而精准的 Java 9 时代语法现代化检查器:它只针对"用Type x = identifier;包装 final/有效 final 变量作为 try-with-resources 资源"这一种模式,给出WARNING级别的提示,并提供包含引用重命名的完整自动修复。它默认关闭,需通过-Xep:TryWithResourcesVariable显式启用。其匹配逻辑、final 判定与修复生成分别依赖TryTreeMatcher、ASTHelpers.isConsideredFinal与SuggestedFixes.renameVariableUsages,整体实现紧凑、行为被三个针对性的重构测试精确锁定,适合作为理解 Error Prone 检查器"匹配—判定—修复"工作流的入门案例。
- 静态分析
- 代码质量
- 开发工具
【免费下载链接】error-prone
Catch common Java mistakes as compile-time errors
相关推荐
Error Prone 检查 ClosingStandardOutputStreams:警惕 try-with-resources 悄悄关闭 System.out / System.err
Error Prone 检查 ClosingStandardOutputStreams:警惕 try with resources 悄悄关闭 System.ou
静态分析代码质量开发工具QuickRecorder macOS 录屏:3 分钟装好,文件小 40%
QuickRecorder macOS 录屏:3 分钟装好,文件小 40% 录一段 10 分钟的教程,文件 800MB,发群里传了半小时。卡住的往往不是"怎么录
静态分析代码质量开发工具Error Prone 的 AutoValueFinalMethods 检查器:为 AutoValue 类的 equals / hashCode / toString 添加 final 修饰
Error Prone 的 AutoValueFinalMethods 检查器:为 AutoValue 类的 equals / hashCode / toStr
静态分析代码质量开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考