热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

JAVA可动态计算表达式的框架非常多,比如:spEL、Aviator、MVEL、EasyRules、jsEL等,这些框架的编码上手程度、功能侧重点及执行性能各有优劣,网上也有大把的

基于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 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 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 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开发人员可以试试;


推荐阅读
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 本文介绍了在C#中SByte类型的GetHashCode方法,该方法用于获取当前SByte实例的HashCode。给出了该方法的语法和返回值,并提供了一个示例程序演示了该方法的使用。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文讨论了如何使用Web.Config进行自定义配置节的配置转换。作者提到,他将msbuild设置为详细模式,但转换却忽略了带有替换转换的自定义部分的存在。 ... [详细]
author-avatar
yangxin
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有