ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Error Prone 的 DuplicateMapKeys 检查:在编译期拦截 Map.ofEntries 重复键

2026/9/29 2:55:23 拓冰建站 浏览量
Error Prone 的 DuplicateMapKeys 检查:在编译期拦截 Map.ofEntries 重复键 静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载导读Map.ofEntries是 JDK 9 引入的不可变 Map 工厂方法它在运行时遇到重复键时会直接抛出IllegalArgumentException。Error Prone 内置的DuplicateMapKeys检查严重级别为 ERROR将这一潜在运行时故障提前到编译期只要检测到Map.ofEntries的参数列表中存在相同键值的Map.entry(...)就会立即报错把运行到才炸的缺陷变成编译就失败。读完本文你将掌握该检查的触发条件、检测原理、源码实现细节以及如何复现、抑制和按需关闭它。问题背景Map.ofEntries 的运行时抛错行为自 JDK 9 起java.util.Map提供了一组便捷的不可变 Map 工厂方法其中Map.ofEntries(Map.Entry? extends K, ? extends V... entries)接受任意数量的键值对条目。与普通的HashMap不同这些工厂方法对入参有严格的约束——当传入多个具有相同 key 的条目时方法会在运行期抛出IllegalArgumentException。下面的代码就存在这样的隐患它向Map.ofEntries传入了两个以Foo为 key 的条目MapString, String map Map.ofEntries( Map.entry(Foo, Bar), Map.entry(Ping, Pong), Map.entry(Kit, Kat), Map.entry(Foo, Bar));这段代码本身是合法的 Java 代码能够通过编译但一旦执行Map.ofEntries就会在运行时因重复键Foo抛出异常。这正是 Error Prone 擅长捕获的编译期看似合法、运行期必然出问题的缺陷模式。检查概览触发条件与错误信息DuplicateMapKeys检查的核心触发条件有两个被检查的调用必须是java.util.Map.ofEntries静态方法调用ofEntries的实参列表中存在两个及以上键值常量相同的Map.entry(...)调用。一旦命中编译期会直接输出错误。检查本身的 summary即默认诊断消息的摘要为Map#ofEntries will throw an IllegalArgumentException if there are any duplicate keys而具体的诊断消息会精确到重复的键值本身duplicate key Foo; Map#ofEntries will throw an IllegalArgumentException与普通 Warning 不同该检查的severity ERROR见 DuplicateMapKeys.java 中的BugPattern声明也就是说重复键问题会被当作编译错误处理直接阻断构建。源码剖析检查是如何实现的匹配器锁定 ofEntries 与 entry 两个静态方法DuplicateMapKeys实现了BugChecker.MethodInvocationTreeMatcher接口并在类中预先定义了匹配java.util.Map.ofEntries与java.util.Map.entry两个静态方法的Matcherprivate static final MatcherExpressionTree METHOD_MATCHER MethodMatchers.staticMethod().onClass(java.util.Map).named(ofEntries); private static final MatcherExpressionTree ENTRY_MATCHER MethodMatchers.staticMethod().onClass(java.util.Map).named(entry);可见检查是精准限定在 JDK 标准库java.util.Map上的——通过MethodMatchers.staticMethod().onClass(...)限定类名通过named(...)限定方法名既不会漏检也不会误伤用户自定义的同名方法。检测主流程常量求值 HashSet 判重matchMethodInvocation是检测的核心逻辑DuplicateMapKeys.java其处理步骤如下先用METHOD_MATCHER判断当前方法调用是否为Map.ofEntries不是则返回Description.NO_MATCH遍历ofEntries的每一个实参表达式tree.getArguments()跳过不是方法调用、或不是Map.entry(...)调用的实参例如直接传入变量、表达式或方法返回值的情况对Map.entry(...)调用取其第一个实参即 key通过ASTHelpers.constValue(...)做编译期常量求值将求值得到的 key 放入HashSet若add返回false即该 key 之前已出现过则立即构建诊断并返回全部遍历完成且无重复返回Description.NO_MATCH。其中ASTHelpers.constValue位于 check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java是 Error Prone 在编译期计算表达式常量值的核心工具它只对编译期可确定值的表达式字符串字面量、数值字面量、static final常量等返回结果。这也是该检查的一个天然边界如果entry的 key 不是编译期常量例如是方法返回值或非常量变量constValue返回null检查会跳过该项而不报错——因为编译器无法在静态分析阶段确定两个非常量 key 是否相等。从源码结构可以推断这是为了在漏报与误报之间做的有意取舍确保错误率接近零。启用方式默认开启的 ERROR 级检查DuplicateMapKeys被注册在 Error Prone 的ENABLED_ERRORS默认启用的错误级检查列表中见 BuiltInCheckerSuppliers.java因此无需任何额外配置使用默认的 Error Prone 配置即可生效。从代码上下文看它和DoubleBraceInitialization、EqualsHashCode、EqualsNaN等同样属于默认开启的 ERROR 级检查BuiltInCheckerSuppliers.java。测试用例正例与反例的验证方式Error Prone 的每个内置检查都配有基于CompilationTestHelper的单元测试DuplicateMapKeys也不例外DuplicateMapKeysTest.java。正例positiveCase构造了与文档完全一致的场景——四个entry中有两个Foo并在源码中用// BUG: Diagnostic contains: Foo注释声明预期诊断验证检查会报出包含键值Foo的错误MapString, String map Map.ofEntries( entry(Foo, Bar), entry(Ping, Pong), entry(Kit, Kat), entry(Foo, Bar));反例negativeCase则将最后一个 key 改为Food四个 key 互不相同验证此时不会产生任何诊断MapString, String map Map.ofEntries( entry(Foo, Bar), entry(Ping, Pong), entry(Kit, Kat), entry(Food, Bar));这两个用例分别覆盖了重复键报错与无重复键放行两条路径是理解该检查行为边界最直接的参考材料。实战触发、抑制与关闭该检查复现步骤在任何使用 JDK 9或 9 以上虚拟机的兼容实现的项目中用 Error Prone 编译下面这段代码即可复现import static java.util.Map.entry; import java.util.Map; class Demo { MapString, String map() { return Map.ofEntries( entry(Foo, Bar), entry(Ping, Pong), entry(Kit, Kat), entry(Foo, Bar)); // duplicate key Foo } }编译输出会得到类似如下错误实际格式取决于构建工具与 Error Prone 版本[ERROR] duplicate key Foo; Map#ofEntries will throw an IllegalArgumentException修复方式也很直接删除或改名重复的条目即可。抑制方式与 Error Prone 的绝大多数检查一样DuplicateMapKeys支持标准的SuppressWarnings抑制BugPattern默认的suppressionAnnotations即为SuppressWarnings见 BugPattern.javaSuppressWarnings(DuplicateMapKeys) MapString, String map Map.ofEntries(/* ... */);抑制时使用的名字取自检查的类名BugPattern.name()未设置时默认使用类名见 BugPattern.java因此既可以用DuplicateMapKeys也可以用其altNames中注册的别名。该检查默认是可被命令行关闭的disableable默认值为true。关闭方式如果你在项目中有意使用重复键覆盖语义虽然这与Map.ofEntries的 API 契约相悖可以通过 Error Prone 的-Xep:系列命令行标志关闭该检查-Xep:DuplicateMapKeys:OFF若只想将其降级为警告而非编译错误可以使用-Xep:DuplicateMapKeys:WARN-Xep标志位接受的取值包括OFF、WARN、ERROR与SUGGESTION四种对应 BugPattern.java 中定义的SeverityLevel枚举。小结DuplicateMapKeys是 Error Prone 中把 JDK 运行时契约变成编译期约束的典型代表检测对象java.util.Map.ofEntries(...)中的重复常量键条目对应 JDK 9 工厂方法的运行时IllegalArgumentException约束检测原理方法匹配器精确锁定Map.ofEntries/Map.entryASTHelpers.constValue做编译期常量求值HashSet判重默认行为作为 ERROR 级检查默认开启注册于ENABLED_ERRORS重复键直接导致编译失败错误消息会带上具体键值边界条件非编译期常量的 key 会被跳过避免误报验证与扩展正/反例测试见 DuplicateMapKeysTest.java实现见 DuplicateMapKeys.java抑制与关闭方式遵循 Error Prone 统一的SuppressWarnings与-Xep机制。对你的代码库而言启用 Error Prone默认配置即包含本检查即可在 CI 的编译阶段拦截此类缺陷让这类写时不易察觉、运行必炸的重复键错误在提交前就暴露出来。赞分享静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载相关推荐Error Prone 的 CannotMockFinalClass 检查在编译期拦截对 final 类的 Mockito 模拟Error Prone 的 CannotMockFinalClass 检查在编译期拦截对 final 类的 Mockito 模拟 CannotMockFina静态分析代码质量开发工具Electric Sync 同步服务从独立 HTTP 端点部署到嵌入 Elixir 应用Electric Sync 同步服务从独立 HTTP 端点部署到嵌入 Elixir 应用 Electric Sync 是构建于 Postgres 逻辑复制之上静态分析代码质量开发工具Error Prone EmptyIf 检查器把误加分号的空 if 语句在编译期拦截为错误Error Prone EmptyIf 检查器把误加分号的空 if 语句在编译期拦截为错误 EmptyIf 是 Error Prone 内置的一项编译期检查器静态分析代码质量开发工具上一篇如何用WarcraftHelper让你的魔兽争霸3焕然一新5个简单步骤解决现代电脑兼容问题下一篇魔兽争霸III现代系统适配与性能优化实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考