bigywater.tech
Java · 218

Tree API:用语法树理解 Java 源码

不执行也不加载代码,只看语法树就能统计复杂度、找出方法调用、定位源码位置。这篇用 JDK 自带的 Tree API 对一段真实的 Java 代码做分析,看 parse 与 analyze 各自能得到什么,以及语法树里没有什么。

184 个
24 行示例代码的语法树节点数
4 / 6 / 5
take / sum / kind 三个方法的圈复杂度
2.4 / 22.8 ms
24 行代码 parse 与 analyze 的耗时

parse():语法树的结构

实测 · 本机执行输出
  包 = demo,import 1 个,顶层类型 1 个
  方法 take  返回 boolean  参数 [String sku, int n]                第 7–13 行,修饰符 [public]
  方法 sum   返回 int      参数 [List<Integer> xs]                 第 14–18 行,修饰符 []
  方法 kind  返回 String   参数 [Object o]                         第 19–21 行,修饰符 []
  方法 run   返回 void     参数 [Runnable r]                       第 22–22 行,修饰符 []
  方法 go    返回 void     参数 []                                 第 23–23 行,修饰符 []
  语法树节点总数 184,种类 41 种;其中 IF 4,METHOD_INVOCATION 8,LAMBDA_EXPRESSION 1,MEMBER_REFERENCE 1,SWITCH_EXPRESSION 1,BINDING_PATTERN 3,CONDITIONAL_EXPRESSION 1
  take 方法第一条语句:Tree.toString() = "if (n <= 0) return false;",按源码位置截取 = "if (n <= 0) return false;"

TreeScanner:复杂度、调用与源码位置

实测 · 本机执行输出
  方法 take  圈复杂度(1 + if/for/?:/case/&&/||)= 4
  方法 sum   圈复杂度(1 + if/for/?:/case/&&/||)= 6
  方法 kind  圈复杂度(1 + if/for/?:/case/&&/||)= 5
  方法 run   圈复杂度(1 + if/for/?:/case/&&/||)= 1
  方法 go    圈复杂度(1 + if/for/?:/case/&&/||)= 1
  所有方法调用及行号:[stock.get @第9行, System.out.println @第10行, stock.put @第11行, xs.isEmpty @第17行, r.run @第22行, run @第23行, System.out.println @第23行, run @第23行]
  其中 System.out.println 调用 2 处
  stock.get(...) 的源码字符区间 [263,277) = "stock.get(sku)",第 9 行第 24 列

注释与错误

实测 · 本机执行输出
  类 Target 的文档注释:订单服务。
  方法 take 的文档注释:扣库存:不足时返回 false。
  方法 sum 的文档注释:(无)
  方法 kind 的文档注释:(无)
  方法 run 的文档注释:(无)
  方法 go 的文档注释:(无)
  语法树里有没有普通的 // 注释:无。仅 /** */ 文档注释能经 DocTrees 取到。
  语义错误(未定义变量、类型不匹配):只做 parse() → 类型声明 1 个,诊断 0 条[]
  语法错误(缺括号与分号):只做 parse() → 类型声明 1 个,诊断 2 条[ [1:18 compiler.err.illegal.start.of.type],  [1:28 compiler.err.expected]]
  同一段语义错误代码再 analyze():诊断 2 条[ [1:28 compiler.err.cant.resolve.location],  [1:69 compiler.err.prob.found.req]]

analyze():类型信息

实测 · 本机执行输出
  变量 stock 声明类型 Map<String, Integer>     → 类型镜像 java.util.Map<java.lang.String,java.lang.Integer>(种类 DECLARED)
  变量 have  声明类型 Integer                  → 类型镜像 java.lang.Integer(种类 DECLARED)
  stock.get    解析到 java.util.Map.get(java.lang.Object),返回类型 V,所属类型种类 INTERFACE
  变量 xs    声明类型 List<Integer>            → 类型镜像 java.util.List<java.lang.Integer>(种类 DECLARED)
  变量 s     声明类型 int                      → 类型镜像 int(种类 INT)
  xs.isEmpty   解析到 java.util.List.isEmpty(),返回类型 boolean,所属类型种类 INTERFACE
  变量 s     声明类型 String                   → 类型镜像 java.lang.String(种类 DECLARED)
  r.run        解析到 java.lang.Runnable.run(),返回类型 void,所属类型种类 INTERFACE
  lambda () -> println("hi") 的目标类型 = java.lang.Runnable
  方法引用 this::go 的目标类型 = java.lang.Runnable
  同一段 24 行代码(新建任务):parse 2.4 ms,analyze 22.8 ms

分步演示

Tree API
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;com.sun.source 是 jdk.compiler 模块的受支持 API(不是内部 API)
    • 没有验证:大型代码库上的分析性能、跨文件符号解析(依赖 classpath 的情况)、com.sun.tools.javac.tree 内部节点与公开 Tree 的差异、对 Java 21 以后新语法的覆盖、基于语法树做源码改写(重新输出)的方式;耗时数字只代表这台机器上的一次运行
    京ICP备2026067502号-1京公网安备11010502063487号