在进行简易计算器开发或表达式求值处理时,常常会遇到这样的需求:手头已有两个数组,一个存放数字,另一个存放运算符,需要按顺序逐一计算出最终结果。听起来简单,但实际编写时,细节处理仍值得深究。
本文所讨论的计算方式,并非我们熟悉的“先乘除后加减”标准数学规则,而是严格从左到右、依次执行的线性计算。例如输入"10+5*3-2",按标准数学规则结果为23;而按此线性规则,它会被解析为((10+5)*3)-2,输出43。两种逻辑各有适用场景,关键在于你的需求决定选择哪种。
下面提供一个清晰、健壮的Java实现方案,可直接用于实际项目。
import ja va.util.*;
public class SequentialCalculator {
/**
* 按顺序(左结合)计算数字与运算符列表
* @param numbers 非空数字字符串列表(至少含1个元素)
* @param operators 运算符字符串列表(长度 = numbers.size() - 1)
* @return 计算结果(double)
*/
public static double evaluate(List numbers, List operators) {
if (numbers.isEmpty()) {
throw new IllegalArgumentException("Numbers list cannot be empty");
}
if (operators.size() != numbers.size() - 1) {
throw new IllegalArgumentException(
"Operators count must be exactly one less than numbers count"
);
}
double result = Double.parseDouble(numbers.get(0));
for (int i = 0; i < operators.size(); i++) {
double nextValue = Double.parseDouble(numbers.get(i + 1));
char op = operators.get(i).charAt(0);
result = switch (op) {
case '+' -> result + nextValue;
case '-' -> result - nextValue;
case '*' -> result * nextValue;
case '/' -> {
if (nextValue == 0.0) {
throw new ArithmeticException("Division by zero");
}
yield result / nextValue;
}
default -> throw new IllegalArgumentException("Unsupported operator: " + op);
};
}
return result;
}
// 使用示例
public static void main(String[] args) {
List numbers = Arrays.asList("10", "5", "3", "2");
List operators = Arrays.asList("+", "*", "-");
double result = evaluate(numbers, operators);
System.out.println(result); // 输出: 43.0 (即 ((10 + 5) * 3) - 2 = 45 - 2 = 43)
}
}
核心逻辑说明
本段代码的实现逻辑非常直观:
- 将第一个数字作为初始结果值;
- 随后遍历每个运算符,依次将当前结果与下一个数字按照运算符进行计算;
- 整个过程严格遵循左结合规则,完全忽略运算符优先级。
仍以"10+5*3-2"为例:先计算10+5=15,接着15*3=45,最后45-2=43。这一结果与标准数学运算截然不同,但正是线性计算流的典型特征。
注意事项
使用该方法时,需特别留意以下几个关键点:
- 数据校验是必不可少的保护机制——operators.size()必须严格等于numbers.size() - 1,否则整个计算逻辑将出现混乱;
- 除零检查必须提前处理,虽然Java会抛出ArithmeticException,但更优雅的做法是主动捕获并给出明确的错误提示;
- 字符串转数字采用Double.parseDouble(),天然支持小数运算。若业务场景仅需整数,可替换为Integer.parseInt()并让方法返回int类型;
- 如果未来需要支持标准运算符优先级(如先乘除后加减),则此线性方案无法满足需求。此时建议采用调度场算法(Shunting Yard)或构建抽象语法树(AST),复杂度会显著提升。
总体而言,该方案的优点在于简洁明了、开箱即用。它非常适合教学级计算器、表达式求值的中间处理步骤,或某些特定协议解析场景。对于希望深入理解运算符绑定与表达式求值流程的开发者来说,这也是一个不错的起点。
