我试图写一个Java例程来计算数学表达式从字符串值,如:

"5 + 3" "10-4 * 5" "(1 + 10) * 3"

我想避免很多如果-then-else语句。 我该怎么做呢?


当前回答

对于JDK1.6,您可以使用内置的Javascript引擎。

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));
    } 
}

其他回答

我想无论你用什么方法做这个都会涉及到很多条件命题。但是对于单个操作,比如在你的例子中,你可以将它限制为4个if语句

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

当你想要处理像“4+5*6”这样的多个操作时,它会变得更加复杂。

如果你试图构建一个计算器,那么我建议分别传递计算的每个部分(每个数字或运算符),而不是作为一个单一的字符串。

我写了算术表达式的eval方法来回答这个问题。它可以做加法、减法、乘法、除法、求幂(使用^符号),以及一些基本函数,如平方根。它支持使用(…)进行分组,并获得正确的操作符优先级和结合规则。

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;
        
        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }
        
        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }
        
        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }
        
        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)` | number
        //        | functionName `(` expression `)` | functionName factor
        //        | factor `^` factor
        
        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }
        
        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else return x;
            }
        }
        
        double parseFactor() {
            if (eat('+')) return +parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus
            
            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                if (!eat(')')) throw new RuntimeException("Missing ')'");
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                if (eat('(')) {
                    x = parseExpression();
                    if (!eat(')')) throw new RuntimeException("Missing ')' after argument to " + func);
                } else {
                    x = parseFactor();
                }
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }
            
            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
            
            return x;
        }
    }.parse();
}

例子:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

输出:7.5(正确)


该解析器是递归下降解析器,因此在内部为其语法中的每个操作符优先级使用单独的解析方法。我故意保持简短,但以下是一些你可能想要扩展的想法:

Variables: The bit of the parser that reads the names for functions can easily be changed to handle custom variables too, by looking up names in a variable table passed to the eval method, such as a Map<String,Double> variables. Separate compilation and evaluation: What if, having added support for variables, you wanted to evaluate the same expression millions of times with changed variables, without parsing it every time? It's possible. First define an interface to use to evaluate the precompiled expression: @FunctionalInterface interface Expression { double eval(); } Now to rework the original "eval" function into a "parse" function, change all the methods that return doubles, so instead they return an instance of that interface. Java 8's lambda syntax works well for this. Example of one of the changed methods: Expression parseExpression() { Expression x = parseTerm(); for (;;) { if (eat('+')) { // addition Expression a = x, b = parseTerm(); x = (() -> a.eval() + b.eval()); } else if (eat('-')) { // subtraction Expression a = x, b = parseTerm(); x = (() -> a.eval() - b.eval()); } else { return x; } } } That builds a recursive tree of Expression objects representing the compiled expression (an abstract syntax tree). Then you can compile it once and evaluate it repeatedly with different values: public static void main(String[] args) { Map<String,Double> variables = new HashMap<>(); Expression exp = parse("x^2 - x + 2", variables); for (double x = -20; x <= +20; x++) { variables.put("x", x); System.out.println(x + " => " + exp.eval()); } } Different datatypes: Instead of double, you could change the evaluator to use something more powerful like BigDecimal, or a class that implements complex numbers, or rational numbers (fractions). You could even use Object, allowing some mix of datatypes in expressions, just like a real programming language. :)


这个答案中的所有代码都已发布到公共领域。玩得开心!

另一种方法是使用Spring表达式语言或SpEL,它在计算数学表达式时做了更多的工作,因此可能会有点过度。您不必使用Spring框架来使用这个表达式库,因为它是独立的。从SpEL文档中复制示例:

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0

这样怎么样:

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

并相应地对其他数学运算符做类似的事情。

解决这个问题的正确方法是使用词法分析器和解析器。您可以自己编写这些页面的简单版本,或者这些页面还包含指向Java词法分析器和解析器的链接。

创建递归下降解析器是非常好的学习练习。