如何使用Java Expression Library(JEL)实现二元表达式快速求值
结论
JEL完全匹配你的使用需求。它和mxParser这类解释执行的表达式库不同,会直接把输入的字符串表达式编译成JVM原生字节码,批量求值的性能和你直接硬编码调用Java Math库的差距极小,运行效率远高于mxParser,非常适合需要传入多组参数反复求值的场景。
具体实现流程
操作核心是把「表达式编译」和「参数传入求值」两个步骤完全拆开,只需要编译一次表达式,后续求值不需要重复做语法解析,这也是JEL性能高的核心原因:
- 第一步:定义变量存储容器,用来存放每次求值传入的x、y值,JEL编译时会直接绑定变量的访问路径,求值时不需要做参数名的哈希查找。
- 第二步:初始化JEL的函数库,直接导入
java.lang.Math下的所有公开静态方法,就可以直接支持sin、cos、sqrt、pow等所有常用数学函数,不需要自己逐个实现。 - 第三步:处理表达式语法兼容:JEL默认的
^运算符是按位异或,不是幂运算,你可以在读取用户输入的表达式后,简单把幂运算写法替换为pow(底数,指数)的形式,就能兼容你示例里的sin(x^2+y^2)这类写法。 - 第四步:传入处理好的表达式字符串,指定返回值为double类型,编译得到可重复执行的表达式对象。
- 第五步:批量求值时,只需要给变量容器里的x、y赋对应的值,调用表达式对象的求值方法即可,不需要重复解析表达式。
可直接运行的参考代码
import gnu.jel.CompiledExpression; import gnu.jel.Library; import gnu.jel.DVMap; // 变量存储容器,JEL编译时会直接绑定这个类里的变量 public class VarContext extends DVMap { public double x; public double y; @Override public String getTypeName(String varName) { if ("x".equals(varName) || "y".equals(varName)) { return "double"; } return null; } } public class JELEvalTest { public static void main(String[] args) throws Exception { VarContext ctx = new VarContext(); // 导入Math类所有静态方法作为可用函数 Class<?>[] staticLibs = {Math.class}; Library lib = new Library(staticLibs, new Class[]{}, null, ctx, null); // 原始表达式是sin(x^2+y^2),替换为JEL支持的pow写法 String expr = "sin(pow(x,2) + pow(y,2))"; // 仅需编译一次,后续可反复求值 CompiledExpression compiledExpr = gnu.jel.Compiler.compile(expr, lib, double.class); // 批量传入不同参数求值 double[][] paramGroups = {{0, 0}, {1, 1}, {Math.PI/4, Math.PI/4}}; for (double[] params : paramGroups) { ctx.x = params[0]; ctx.y = params[1]; double res = (double) compiledExpr.evaluate(ctx); System.out.printf("x=%.2f, y=%.2f, 计算结果=%.4f%n", ctx.x, ctx.y, res); } } }
性能优化提示
- 不要把编译逻辑放到求值循环里,只要表达式不变,
CompiledExpression对象可以一直复用,这是保证性能的核心。 - 直接导入Math类的原生方法比自己包装一层自定义函数性能高30%以上,没有特殊需求不要额外封装数学函数。
- 如果需要支持更多自定义函数,只需要把包含对应静态方法的类加到staticLibs数组里即可,JEL会自动识别方法签名,不需要额外注册。
内容的提问来源于stack exchange,提问作者Rango
相关产品推荐
相关产品推荐

