基于javascript引擎封装实现算术表达式计算工具类

基于javascript引擎封装实现算术表达式计算工具类

JAVA可动态计算表达式的框架非常多,比如:spEL、Aviator、MVEL、EasyRules、jsEL等,这些框架的编码上手程度、功能侧重点及执行性能各有优劣,网上也有大把的学习资料及示例代码,我这里也不在赘述了,本文要介绍的是直接借助于JDK中自带的ScriptEngineManager,使用javascript Engine来动态计算表达式,编码简单及执行性能接近原生JAVA,完全满足目前我公司的产品系统需求(通过配置计算公式模板,然后将实际的值带入公式中,最后计算获得结果),当然在实际的单元测试中发现,由于本质是使用的javascript 语法进行表达式计算,若有小数,则会出现精度不准确的情况(网上也有人反馈及给出了相应的解决方案),为了解决该问题,同时又不增加开发人员的使用复杂度,故我对计算过程进行了封装,计算方法内部会自动识别出表达式中的变量及数字部份,然后所有参与计算的值均通过乘以10000转换为整数后进行计算,计算的结果再除以10000以还原真实的结果,具体封装的工具类代码如下:

public class JsExpressionCalcUtils {

        private static ScriptEngine getJsEngine() {
            ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
            return scriptEngineManager.getEngineByName("javascript");
        }

        /**
         * 普通计算,若有小数计算则可能会出现精度丢失问题,整数计算无问题
         * @param jsExpr
         * @param targetMap
         * @return
         * @throws ScriptException
         */
        public static Double calculate(String jsExpr, Map<String, ? extends Number> targetMap) throws ScriptException {
            ScriptEngine jsEngine = getJsEngine();
            SimpleBindings bindings=new SimpleBindings();
            bindings.putAll(targetMap);
            return (Double) jsEngine.eval(jsExpr, bindings);
        }

        /**
         * 精确计算,支持小数或整数的混合运算,不会存在精度问题
         * @param jsExpr
         * @param targetMap
         * @return
         * @throws ScriptException
         */
        public static Double exactCalculate(String jsExpr, Map<String, ? extends Number> targetMap) throws ScriptException {
            String[] numVars = jsExpr.split("[()*\-+/]");
            numVars = Arrays.stream(numVars).filter(StringUtils::isNotEmpty).toArray(String[]::new);

            double fixedValue = 10000D;
            StringBuilder stringBuilder = new StringBuilder();
            for (String item : numVars) {
                Number numValue = targetMap.get(item);
                if (numValue == null) {
                    if (NumberUtils.isNumber(item)) {
                        jsExpr = jsExpr.replaceFirst("\b" + item + "\b", String.valueOf(Double.parseDouble(item) * fixedValue));
                        continue;
                    }
                    numValue = 0;
                }
                stringBuilder.append(String.format(",%s=%s",item, numValue.doubleValue() * fixedValue));
            }

            ScriptEngine jsEngine = getJsEngine();
            String calcJsExpr = String.format("var %s;%s;", stringBuilder.substring(1), jsExpr);
            double result = (double) jsEngine.eval(calcJsExpr);
            System.out.println("calcJsExpr:" + calcJsExpr +",result:" + result);
            return result / fixedValue;
        }

    }

如上代码所示,calculate方法是原生的js表达式计算,若有小数则可能会有精度问题,而exactCalculate方法是我进行封装转换为整数进行计算后再还原的方法,无论整数或小数进行计算都无精度问题,具体见如下单元测试的结果:

    @Test
    public void testJsExpr() throws ScriptException {
        Map<String,Double> numMap=new HashMap<>();
        numMap.put("a",0.3D);
        numMap.put("b",0.1D);
        numMap.put("c",0.2D);

        //0.3-(0.1+0.2) 应该为 0.0,实际呢?
        String expr="a-(b+c)";
        Double result1= JsExpressionCalcUtils.calculate(expr,numMap);
        System.out.println("result1:" + result1);

        Double result2= JsExpressionCalcUtils.exactCalculate(expr,numMap);
        System.out.println("result2:" + result2);

    }

result1:-5.551115123125783E-17 —这不符合预期结果

calcJsExpr:var a=3000.0,b=1000.0,c=2000.0;a-(b+c);,result:0.0
result2:0.0 —符合预期结果

顺便说一下,.NET(C#)语言也是支持执行javascript表达式的哦,当然也可以实现上述的求值表达式工具类,实现思路相同,有兴趣的.NET开发人员可以试试;

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 基于javascript引擎封装实现算术表达式计算工具类