查看原文
其他

一文了解如何用 Java 进行算术表达式计算

ImportNew ImportNew 2019-12-01

(给ImportNew加星标,提高Java技能)

编译:ImportNew/唐尤华


如何用Java计算"5+3"、"10-40"、"10*3"这样的算术表达式?本文介绍了可使用的常见方法及各自优缺点。


1. JDK Javascript引擎


JDK1.6开始,提供了Javascript脚本引擎。可以把 String 形式的字符串通过 engine.eval() 方法执行计算。示例如下:


import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Test {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("JavaScript");
String foo = "40+2";
System.out.println(engine.eval(foo));
}
}


Java中的ScriptEngine理论上支持符合ECMAScript规范的语言。要确认当前Java支持哪些语言,可以执行下列代码:


import java.util.List;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory;


public class Test {

public static void main(String[] args)
{
ScriptEngineManager mgr = new ScriptEngineManager();
List<ScriptEngineFactory> factories = mgr.getEngineFactories();
for (ScriptEngineFactory factory : factories)
{
System.out.println("ScriptEngineFactory Info");
String engName = factory.getEngineName();
String engVersion = factory.getEngineVersion();
String langName = factory.getLanguageName();
String langVersion = factory.getLanguageVersion();
System.out.printf("\tScript Engine: %s (%s)\n", engName, engVersion);
List<String> engNames = factory.getNames();
for (String name : engNames)
{
System.out.printf("\tEngine Alias: %s\n", name);
}
System.out.printf("\tLanguage: %s (%s)\n", langName, langVersion);
}
}
}


执行完毕会看到ScriptEngine支持的语言。在我的机器上(java version "1.8.0_191")运行会看到下面的结果:


ScriptEngineFactory Info
Script Engine: Oracle Nashorn (1.8.0_191)
Engine Alias: nashorn
Engine Alias: Nashorn
Engine Alias: js
Engine Alias: JS
Engine Alias: JavaScript
Engine Alias: javascript
Engine Alias: ECMAScript
Engine Alias: ecmascript
Language: ECMAScript (ECMA - 262 Edition 5.1)


JDK支持的JavaScript来自Mozilla Rhino ECMAScript。Rhino 是一个完全使用Java语言编写的开源JavaScript实现。Rhino通常用在Java程序中,为最终用户提供脚本化能力。它被作为J2SE 6上的默认Java脚本化引擎。


既然支持脚本,那么就不仅限于普通的算术运算符,还可以使用Javascript中的Math函数。比如像下面这样:


String foo = "Math.cos(60)+Math.sin(30)";
System.out.println(engine.eval(foo));
// 输出 -1.9404446045080181


还可以支持变量。比如上面的例子中,两个角度分别用变量表示:


engine.put("angle1", 60);
engine.put("angle2", 30);
String bar = "Math.cos(angle1)+Math.sin(angle2)";
System.out.println(engine.eval(bar));
// 输出 -1.9404446045080181


这种方法的缺点在于,脚本本身可能有潜在的安全性问题,容易收到攻击。不推荐在服务端使用。


2. 第三方开发库


在Java的世界里,永远有丰富的第三方开发库可供选择,计算算术表达式也一样。下面列举一些常用的开发库。

JEXL


http://commons.apache.org/proper/commons-jexl/


Java Expression Language (JEXL) 是一个表达式语言引擎,可以用来在应用或者框架中使用。使用示例:


// 假设有一个名为 jexl 的类, 初始化了 JexlEngine 实例:
// 创建一个表达式对象进行计算
String calculateTax = taxManager.getTaxCalc(); //e.g. "((G1 + G2 + G3) * 0.1) + G4";
Expression e = jexl.createExpression( calculateTax );

// 填写上下文
JexlContext context = new MapContext();
context.set("G1", businessObject.getTotalSales());
context.set("G2", taxManager.getTaxCredit(businessObject.getYear()));
context.set("G3", businessObject.getIntercompanyPayments());
context.set("G4", -taxManager.getAllowances());
// ...

// 得到计算结果
Number result = (Number) e.evaluate(context);


更多内容可参见官网示例。


mXparser


http://mathparser.org/


mXparser的优点是支持多平台,Java、Android、dotNET等。


使用示例:


package mxparser.helloworld;

import org.mariuszgromada.math.mxparser.*;

public class HelloWorld {
public static void main(String[] args) {
Expression eh = new Expression("5^2 * 7^3 * 11^1 * 67^1 * 49201^1");
Expression ew = new Expression("71^1 * 218549^1 * 6195547^1");
String h = mXparser.numberToAsciiString( eh.calculate() );
String w = mXparser.numberToAsciiString( ew.calculate() );
mXparser.consolePrintln(h + " " + w);
}
}


EvalEx


https://github.com/uklimaschewski/EvalEx


使用示例:


new Expression("123456789 + 123456789").eval(); // 246913600

new Expression("123456789 + 123456789")
.setPrecision(12)
.eval(); // 246913578


用于Java算术表达式计算的第三方库还有许多,这里不再一一列举。


3. 自己动手


要计算算术表达式计算,还可以自己动手实现Parser。有许多有名的算法,比如Dijkstra的双堆栈算法计算表达式。参考实现如下:


/******************************************************************************
* Compilation: javac Evaluate.java
* Execution: java Evaluate
* Dependencies: Stack.java
*
* Evaluates (fully parenthesized) arithmetic expressions using
* Dijkstra's two-stack algorithm.
*
* % java Evaluate
* ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
* 101.0
*
* % java Evaulate
* ( ( 1 + sqrt ( 5 ) ) / 2.0 )
* 1.618033988749895
*
*
* Note: the operators, operands, and parentheses must be
* separated by whitespace. Also, each operation must
* be enclosed in parentheses. For example, you must write
* ( 1 + ( 2 + 3 ) ) instead of ( 1 + 2 + 3 ).
* See EvaluateDeluxe.java for a fancier version.
*
*
* Remarkably, Dijkstra's algorithm computes the same
* answer if we put each operator *after* its two operands
* instead of *between* them.
*
* % java Evaluate
* ( 1 ( ( 2 3 + ) ( 4 5 * ) * ) + )
* 101.0
*
* Moreover, in such expressions, all parentheses are redundant!
* Removing them yields an expression known as a postfix expression.
* 1 2 3 + 4 5 * * +
*
*
******************************************************************************/


public class Evaluate {
public static void main(String[] args) {
Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>();

while (!StdIn.isEmpty()) {
String s = StdIn.readString();
if (s.equals("(")) ;
else if (s.equals("+")) ops.push(s);
else if (s.equals("-")) ops.push(s);
else if (s.equals("*")) ops.push(s);
else if (s.equals("/")) ops.push(s);
else if (s.equals("sqrt")) ops.push(s);
else if (s.equals(")")) {
String op = ops.pop();
double v = vals.pop();
if (op.equals("+")) v = vals.pop() + v;
else if (op.equals("-")) v = vals.pop() - v;
else if (op.equals("*")) v = vals.pop() * v;
else if (op.equals("/")) v = vals.pop() / v;
else if (op.equals("sqrt")) v = Math.sqrt(v);
vals.push(v);
}
else vals.push(Double.parseDouble(s));
}
StdOut.println(vals.pop());
}
}


总结


用Java进行算术表达式计算可以采用JDK提供的Javascript引擎、第三方库,也可以使用自定义Parser。可以根据自己的需要和项目特点自行选择合适解决方案。


祝编程愉快!


参考文章



推荐阅读

(点击标题可跳转阅读)

Java 表达式之谜:为什么 index 增加了两次?

Java 8 - Stream 集合操作快速上手

一台 Java 服务器可以跑多少个线程?


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

好文章,我在看❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存