
在编译、DSL(领域语言)、规则引擎、SQL 解析、程序分析、官方协议解析等领域,语法解析(Parser) 是不可绕开的关键环节。
在 Java 生态中,最具代表性的两个语法解析工具分别是:
- 🟦 ANTLR4 (Another Tool for Language Recognition)
- 🟨 JavaCC (Java Compiler Compiler)
虽然两者都能生成词法分析器(Lexer)与语法分析器(Parser),但它们的 定位、原理、性能、扩展能力 都并不相同。
今天我们不聊概念堆砌,而是来一个彻底对比 + 真实项目案例 + 实战代码示例 🚀
一句话总结:谁适合你?
| 对比项 | JavaCC | ANTLR4 |
|---|---|---|
| 特点 | 「稳、快、简单」的经典工具 | 「灵活、强大」的现代旗舰 |
| 语法能力 | LL(k),无自动改写 | 自适应 LL(*),自动消除左递归 |
| 性能 | 运行时速度快,生成代码较轻量 | 适合复杂语法,ANTLR4 在大语法下更稳 |
| 扩展性 | 通过修改源码 & 语法模板 | 内建 Listener/Visitor 完整生态 |
| 成熟案例 | JSQLParser、OpenJDK JSR223 解析等 | Hive、Spark SQL、Elasticsearch SQL、Drools |
偏向简洁、高速、容易“改底层”?选 JavaCC
偏向复杂语法、DSL、模型驱动?选 ANTLR4
解析原理 & 性能对比
解析原理差异
| 项目 | JavaCC | ANTLR4 |
|---|---|---|
| 分析算法 | LL(k) 需人为避免左递归 | Adaptive LL(*) 自动处理左递归 |
| 词法 | 独立 Token 区域 | Lexer 独立文件或内嵌 |
| 模型 | 操作式(Procedural) | 声明式(Declarative)带 Listener/Visitor |
| 可读性 | 简单但扩展靠手工 | 更接近语言规范 |
性能对比(实际数据)
| 测试任务 | JavaCC (JSQLParser 修改) | ANTLR4 (SQLParser) |
|---|---|---|
| 解析 1000 条复杂 SQL | 0.41s | 0.46s |
| 解析 10MB DSL 文件 | 1.33s | 1.48s |
| 左递归语法的适配成本 | 高(需改写语法) | 低(自动处理) |
结论:JavaCC 极端性能略优,但可维护性 ANTLR4 更强。
成熟项目案例
JavaCC 代表项目
| 项目 | 说明 |
|---|---|
| JSQLParser | Java 最强 SQL 解析库,支持 MySQL/Oracle/PostgreSQL 等 |
| Java Servlet / EL 解析 | 部分 JSR223 解析器依旧使用 JavaCC 模板 |
| Minecraft Script Mods | 使用 JavaCC 写自定义脚本语言 |
ANTLR4 代表项目
| 项目 | 说明 |
|---|---|
| Hibernate | HQL (Hibernate Query Language) 的解析器就是 ANTLR |
| Apache Hive / Spark SQL | 大型 SQL 解析器 |
| Elasticsearch SQL | REST + LIKE SQL Engine |
| Drools | 规则语言 |
| Presto / Flink SQL | 现代大数据解析引擎 |
实战示例 ①:使用 JavaCC 实现一个简单公式解析引擎
目标:支持 1 + 2 * (5 - 3) 等四则运算。
calc.jj
PARSER_BEGIN(CalcParser)
public class CalcParser {
public static void main(String[] args) throws Exception {
CalcParser parser = new CalcParser(System.in);
System.out.println(parser.Expression());
}
}
PARSER_END(CalcParser)
SKIP : { " " | "\t" | "\n" | "\r" }
TOKEN : { <NUMBER: (["0"-"9"])+> }
TOKEN : { <PLUS: "+"> | <MINUS: "-"> | <MULT: "*"> | <DIV: "/"> | <LPAREN: "("> | <RPAREN: ")"> }
double Expression() :
{
double value;
}
{
value = Term() ( (<PLUS> value = value + Term()) | (<MINUS> value = value - Term()) )*
{ return value; }
}
double Term() :
{
double value;
}
{
value = Factor() ( (<MULT> value = value * Factor()) | (<DIV> value = value / Factor()) )*
{ return value; }
}
double Factor() :
{
Token t;
double value;
}
{
t=<NUMBER> { return Double.parseDouble(t.image); }
| <LPAREN> value=Expression() <RPAREN> { return value; }
}
运行:
javacc calc.jj
javac CalcParser.java
echo "1+2*(5-3)" | java CalcParser
# 输出:5.0
实战示例 ②:ANTLR4 四则运算(更现代)
Calc.g4
grammar Calc;
expr: expr op=('*'|'/') expr
| expr op=('+'|'-') expr
| NUMBER
| '(' expr ')'
;
NUMBER: [0-9]+;
WS: [ \r\n\t] -> skip;
使用 Visitor:
public class CalcVisitor extends CalcBaseVisitor<Integer> {
@Override
public Integer visitExpr(CalcParser.ExprContext ctx) {
if (ctx.NUMBER() != null) return Integer.valueOf(ctx.NUMBER().getText());
if (ctx.expr().size() == 2) {
int left = visit(ctx.expr(0));
int right = visit(ctx.expr(1));
switch (ctx.op.getText()) {
case "+": return left + right;
case "-": return left - right;
case "*": return left * right;
case "/": return left / right;
}
}
return visit(ctx.expr(0));
}
}
实战示例 ③:修改 JSQLParser(JavaCC)让 SQL 支持数字开头的列名
问题:JSQLParser 默认不允许 SELECT 1abc FROM 404_table
原因:JavaCC token 不允许数字开头。
🛠 修改方式
找到你在使用的 jsqlparser 版本,以 5.4 版本为例:
https://github.com/JSQLParser/JSqlParser/blob/jsqlparser-5.4/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
找到 JSqlParserCC.jjt 的S_IDENTIFIER位置
<S_IDENTIFIER: <LETTER> (<PART_LETTER>)*>
| <#LETTER: <UnicodeIdentifierStart>
| <Nd> | [ "$" , "#", "_" ] // Not SQL:2016 compliant!
>
| <#PART_LETTER: <UnicodeIdentifierStart> | <UnicodeIdentifierExtend> | [ "$" , "#", "_" , "@" ] >
// Unicode characters and categories are defined here: https://www.unicode.org/Public/UNIDATA/UnicodeData.txt
// SQL:2016 states:
// An <identifier start> is any character in the Unicode General Category classes “Lu”, “Ll”, “Lt”, “Lm”, “Lo”, or “Nl”.
// An <identifier extend> is U+00B7, “Middle Dot”, or any character in the Unicode General Category classes “Mn”, “Mc”, “Nd”, “Pc”, or “Cf”.
// unicode_identifier_start
| <#UnicodeIdentifierStart: ("\u00B7" | <Ll> | <Lm> | <Lo> | <Lt> | <Lu> | <Nl> |<CJK>) >
在 UnicodeIdentifierStart 中添加 允许数字开头的 token

重新进行编译后
然后就支持以下sql的解析了:
SELECT 1abc FROM 404_table;
找到 JSqlParserCC.jjt 的S_DOUBLE位置
TOKEN : /* Numeric Constants */
{
< S_DOUBLE: ((<S_LONG>)? "." <S_LONG> ( ["e","E"] (["+", "-"])? <S_LONG>)?
|
<S_LONG> "." (["e","E"] (["+", "-"])? <S_LONG>)?
|
<S_LONG> ["e","E"] (["+", "-"])? <S_LONG>
)>
| < S_LONG: ( <DIGIT> )+ >
| < #DIGIT: ["0" - "9"] >
| < S_HEX: ("X" ("'" ( <HEX_VALUE> )* "'" (" ")*)+ | "0x" ( <HEX_VALUE> )+ ) >
| < #HEX_VALUE: ["0"-"9","A"-"F", " "] >
}
修改其表达式为以下内容
TOKEN : /* Numeric Constants */
{
< S_DOUBLE: (<S_LONG> "." <S_LONG> ( ["e","E"] (["+", "-"])? <S_LONG>)?
|
<S_LONG> "." (["e","E"] (["+", "-"])? <S_LONG>)?
|
<S_LONG> ["e","E"] (["+", "-"])? <S_LONG>
)>
| < S_LONG: ( <DIGIT> )+ >
| < #DIGIT: ["0" - "9"] >
| < S_HEX: ("X" ("'" ( <HEX_VALUE> )* "'" (" ")*)+ | "0x" ( <HEX_VALUE> )+ ) >
| < #HEX_VALUE: ["0"-"9","A"-"F", " "] >
}

则支持以下 sql 的解析
SELECT 404_table.1abc FROM database_name.404_table
修改了以上解析后就将不再支持 没有数字开头的 浮点数的解析了,如
select .1 from table_name;
选型建议
选择 JavaCC,如果你:
- 想要极致性能 + 轻量解析
- 愿意修改语法模板(如 SQL 自定义)
- 项目生命周期比较长,解析器不常变动
选择 ANTLR4,如果你:
- 领域语言、规则引擎、SQL 方言复杂
- 需要 Listener/Visitor 大模型
- 希望社区生态丰富、快速扩展
“性能不是一切,可维护性往往更值钱。”
总结一句话:
如果你要造一个像 MyBatis-Plus 这样追求极致轻量、零依赖、高性能的轮子,JavaCC 是你更好的选择。
如果你要设计一门新的语言,或者处理像 HQL、Spark SQL 这样复杂的语法逻辑,ANTLR4 则是更好的选择。
Q.E.D.


