Java中规模软件开发实训——简单计算器制作

'# Java中规模软件开发实训——简单计算器制作

一、背景与问题

在软件开发实训中,计算器作为常见的应用场景,常被用作练习面向对象设计、异常处理、表达式解析等技术的载体。传统实现方式往往将计算逻辑简单封装为静态方法,但随着功能复杂度增加(如支持括号、函数嵌套、错误处理等),这种做法会暴露诸多问题。

在中规模开发中,我们需要考虑:

  • 表达式解析的算法选择
  • 多线程处理的潜在需求
  • 用户输入的健壮性处理
  • 历史记录的持久化机制
  • 跨平台的UI实现

传统单体计算器设计存在以下局限:

  1. 无法处理复杂的运算符优先级
  2. 无法验证表达式合法性
  3. 缺乏异常处理机制
  4. 无法扩展高级功能(如函数计算)

二、基本原理

1. 表达式解析的算法选择

计算器的核心挑战在于表达式解析。常见的解决方案有:

  • 递归下降解析法(Recursive Descent Parsing)
  • 基于栈的Shunting Yard算法
  • 使用第三方库(如Javaluator)

Shunting Yard算法因其对运算符优先级的处理能力,成为中规模计算器的优选方案。该算法通过维护两个栈(运算符栈和输出栈)实现表达式转换,具体流程如下:

  1. 从左到右扫描表达式
  2. 数字直接入输出栈
  3. 运算符根据优先级与栈顶元素比较决定入栈或弹出
  4. 左括号直接入栈,右括号触发栈顶元素弹出直到左括号
  5. 最后将运算符栈中剩余元素弹出

2. 健壮性设计原则

在中规模系统中,需要考虑:

  • 输入验证:对非法字符、非法格式的处理
  • 异常处理:捕获除零、无效操作等异常
  • 代码可维护性:遵循单一职责原则
  • 扩展性:支持后续功能扩展

三、环境准备

开发环境要求:

  • JDK 17+
  • IDE(推荐IntelliJ IDEA或Eclipse)
  • Maven项目结构(推荐使用Spring Boot简化开发)

项目结构建议:

src
├── main
│   ├── java
│   │   └── calculator
│   │       ├── core
│   │       │   ├── ExpressionEvaluator.java
│   │       │   ├── Tokenizer.java
│   │       │   └── Parser.java
│   │       └── service
│   │           └── CalculatorService.java
│   └── resources
│       └── application.properties
└── test
    └── java
        └── calculator
            └── core
                └── ExpressionEvaluatorTest.java

四、核心实现

1. 表达式分词器(Tokenizer)

public class Tokenizer {
    public static List<Token> tokenize(String expression) {
        List<Token> tokens = new ArrayList<>();
        StringBuilder currentNumber = new StringBuilder();
        boolean isNegative = false;
        
        for (int i = 0; i < expression.length(); i++) {
            char c = expression.charAt(i);
            
            if (Character.isDigit(c) || c == '.') {
                currentNumber.append(c);
            } else if (c == '+' || c == '-' || c == '*' || c == '/') {
                if (!currentNumber.isEmpty()) {
                    tokens.add(new Token(TokenType.NUMBER, currentNumber.toString()));
                    currentNumber.setLength(0);
                }
                tokens.add(new Token(TokenType.OPERATOR, String.valueOf(c)));
            } else if (c == '(' || c == ')') {
                if (!currentNumber.isEmpty()) {
                    tokens.add(new Token(TokenType.NUMBER, currentNumber.toString()));
                    currentNumber.setLength(0);
                }
                tokens.add(new Token(TokenType.PARENTHESIS, String.valueOf(c)));
            } else if (c == ' ') {
                continue;
            } else {
                throw new IllegalArgumentException("Invalid character: " + c);
            }
        }
        
        if (!currentNumber.isEmpty()) {
            tokens.add(new Token(TokenType.NUMBER, currentNumber.toString()));
        }
        
        return tokens;
    }
}

关键点解析:

  • 识别数字和小数点
  • 处理运算符和括号
  • 严格校验非法字符
  • 支持负数处理(需额外扩展)

2. 表达式解析器(Parser)

public class Parser {
    private List<Token> tokens;
    private int index = 0;
    
    public Parser(List<Token> tokens) {
        this.tokens = tokens;
    }
    
    public List<String> parse() {
        List<String> postfix = new ArrayList<>();
        Deque<String> operatorStack = new ArrayDeque<>();
        
        while (index < tokens.size()) {
            Token token = tokens.get(index);
            
            switch (token.getType()) {
                case NUMBER:
                    postfix.add(token.getValue());
                    index++;
                    break;
                case OPERATOR:
                    String op = token.getValue();
                    while (!operatorStack.isEmpty() && 
                           getPrecedence(op) <= getPrecedence(operatorStack.peek())) {
                        postfix.add(operatorStack.pop());
                    }
                    operatorStack.push(op);
                    index++;
                    break;
                case PARENTHESIS:
                    if (token.getValue().equals("(")) {
                        operatorStack.push(token.getValue());
                        index++;
                    } else {
                        while (!operatorStack.isEmpty() && 
                               !operatorStack.peek().equals("(")) {
                            postfix.add(operatorStack.pop());
                        }
                        if (!operatorStack.isEmpty() && operatorStack.peek().equals("(")) {
                            operatorStack.pop(); // 弹出左括号
                        }
                        index++;
                    }
                    break;
                default:
                    throw new IllegalArgumentException("Unknown token: " + token.getType());
            }
        }
        
        while (!operatorStack.isEmpty()) {
            postfix.add(operatorStack.pop());
        }
        
        return postfix;
    }
    
    private int getPrecedence(String op) {
        return switch (op) {
            case "+" -> 1;
            case "-" -> 1;
            case "*" -> 2;
            case "/" -> 2;
            default -> 0;
        };
    }
}

关键点解析:

  • 使用双栈算法处理运算符优先级
  • 正确处理括号嵌套
  • 支持多层运算符
  • 异常处理机制

3. 表达式计算器(Evaluator)

public class ExpressionEvaluator {
    public static double evaluate(String expression) {
        List<Token> tokens = Tokenizer.tokenize(expression);
        List<String> postfix = new Parser(tokens).parse();
        Deque<Double> stack = new ArrayDeque<>();
        
        for (String token : postfix) {
            if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
                if (stack.size() < 2) {
                    throw new IllegalArgumentException("Invalid expression: insufficient operands");
                }
                double b = stack.pop();
                double a = stack.pop();
                
                switch (token) {
                    case "+": stack.push(a + b); break;
                    case "-": stack.push(a - b); break;
                    case "*": stack.push(a * b); break;
                    case "/": 
                        if (b == 0) {
                            throw new ArithmeticException("Division by zero");
                        }
                        stack.push(a / b); 
                        break;
                }
            } else {
                stack.push(Double.parseDouble(token));
            }
        }
        
        if (stack.size() != 1) {
            throw new IllegalArgumentException("Invalid expression: too many operands");
        }
        
        return stack.pop();
    }
}

关键点解析:

  • 使用后缀表达式计算
  • 异常处理机制
  • 支持除零异常
  • 数值精度控制

五、完整案例

1. 简易计算器应用(GUI版本)

public class CalculatorApp {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Calculator");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        
        JTextField inputField = new JTextField();
        inputField.setEditable(false);
        inputField.setBounds(10, 10, 300, 30);
        
        JButton calculateButton = new JButton("Calculate");
        calculateButton.setBounds(10, 50, 100, 30);
        
        JTextArea resultArea = new JTextArea();
        resultArea.setEditable(false);
        resultArea.setBounds(10, 90, 300, 150);
        
        calculateButton.addActionListener(e -> {
            try {
                String expression = inputField.getText();
                double result = ExpressionEvaluator.evaluate(expression);
                resultArea.setText("Result: " + result);
            } catch (Exception ex) {
                resultArea.setText("Error: " + ex.getMessage());
            }
        });
        
        frame.setLayout(null);
        frame.add(inputField);
        frame.add(calculateButton);
        frame.add(resultArea);
        frame.setVisible(true);
    }
}

2. 控制台测试案例

public class TestCalculator {
    public static void main(String[] args) {
        String[] testExpressions = {
            "3 + 5",
            "10 * (2 + 3)",
            "5 / 0",
            "2 + 3 * (4 - 1)",
            "invalid expression",
            "(2 + 3) * 4",
            "100 - 50 / 2"
        };
        
        for (int i = 0; i < testExpressions.length; i++) {
            System.out.println("Test case " + (i+1) + ": " + testExpressions[i]);
            try {
                System.out.println("Result: " + ExpressionEvaluator.evaluate(testExpressions[i]));
            } catch (Exception ex) {
                System.out.println("Error: " + ex.getMessage());
            }
        }
    }
}

3. 异常处理测试

public class ExceptionTest {
    public static void main(String[] args) {
        String[] errorExpressions = {
            "3 + * 5",
            "10 + 5 /",
            "2 + 3 * (",
            "100 / 0",
            "2 + 3 * (4 - 5)"
        };
        
        for (int i = 0; i < errorExpressions.length; i++) {
            System.out.println("Error test " + (i+1) + ": " + errorExpressions[i]);
            try {
                ExpressionEvaluator.evaluate(errorExpressions[i]);
                System.out.println("No error");
            } catch (Exception ex) {
                System.out.println("Error: " + ex.getMessage());
            }
        }
    }
}

六、源码解析

1. 表达式分词器的实现细节

在Tokenizer类中,通过逐字符处理实现分词:

  • 数字和小数点直接加入数字token
  • 运算符和括号单独处理
  • 空格直接跳过
  • 非法字符抛出异常

此设计确保了输入的合法性校验,避免后续解析阶段出现未处理的异常。

2. 表达式解析器的算法实现

Parser类中的parse方法实现了Shunting Yard算法:

  • 使用双栈结构处理运算符优先级
  • 正确处理括号嵌套
  • 支持多层运算符
  • 严格校验输入合法性

该算法的时间复杂度为O(n),适用于中规模计算需求。

3. 表达式计算器的异常处理

ExpressionEvaluator类中的异常处理机制:

  • 除零异常检测
  • 运算符数量校验
  • 数值转换异常处理
  • 末尾运算符检测

这些机制确保了计算过程的健壮性。

七、进阶使用

1. 支持函数计算

扩展ExpressionEvaluator以支持函数计算:

public class FunctionEvaluator {
    public static double evaluateWithFunctions(String expression) {
        // 实现函数解析逻辑
        // 支持sin、cos、sqrt等数学函数
        // 需要处理函数调用的语法结构
    }
}

2. 历史记录功能

添加历史记录功能:

public class HistoryManager {
    private List<String> history = new ArrayList<>();
    
    public void addHistory(String expression, double result) {
        history.add("Expression: " + expression + ", Result: " + result);
    }
    
    public List<String> getHistory() {
        return history;
    }
}

3. 跨平台UI实现

使用JavaFX实现跨平台UI:

public class JavaFXCalculator {
    public static void main(String[] args) {
        launch(args);
    }
    
    public static void start(Stage primaryStage) {
        // 实现JavaFX界面
    }
}

八、性能与工程实践

1. 性能优化策略

  • 使用更高效的字符串处理算法
  • 预处理输入表达式
  • 使用缓存机制
  • 使用线程池处理并发计算
  • 优化算法时间复杂度

2. 异常处理优化

  • 增加详细的错误信息
  • 使用自定义异常类
  • 分层异常处理
  • 日志记录异常信息

3. 安全性考虑

  • 输入过滤机制
  • 防止注入攻击
  • 防止恶意输入导致程序崩溃
  • 使用安全的数值转换方法

4. 代码可维护性

  • 使用设计模式(如策略模式)
  • 分层架构设计
  • 单元测试覆盖
  • 文档注释

九、常见问题与踩坑

1. 常见错误及解决办法

错误1:未处理括号导致计算错误

// 错误代码
ExpressionEvaluator.evaluate("10 * (2 + 3)");

解决方法:确保解析器正确处理括号嵌套

错误2:运算符优先级错误

// 错误代码
ExpressionEvaluator.evaluate("2 + 3 * 4");

解决方法:确保解析器正确识别运算符优先级

错误3:未处理负数

// 错误代码
ExpressionEvaluator.evaluate("-5 + 10");

解决方法:在分词器中增加负数处理逻辑

2. 性能瓶颈分析

表达式解析过程中,字符串处理和栈操作可能导致性能瓶颈。可以通过以下方式优化:

  • 预处理表达式
  • 使用更高效的数据结构
  • 减少不必要的字符串操作
  • 使用缓存机制

3. 安全风险分析

用户输入可能包含恶意代码,需进行严格校验:

// 错误代码
ExpressionEvaluator.evaluate("Math.sqrt(-1)");

解决方法:增加输入校验,防止非法函数调用

十、最佳实践

  1. 使用设计模式:采用观察者模式处理历史记录,使用策略模式处理不同计算方式
  2. 分层架构设计:将解析、计算、UI分离,提高可维护性
  3. 严格的输入校验:在分词阶段即进行合法性校验
  4. 异常处理机制:为每个操作增加异常处理
  5. 单元测试覆盖:为每个功能模块编写单元测试
  6. 文档注释:为每个类和方法添加详细注释
  7. 性能优化:针对关键路径进行优化

十一、总结

通过本实训项目,我们深入探讨了中规模计算器开发的关键技术点。从表达式解析的算法选择,到健壮性设计,再到性能优化和安全性考虑,每个环节都需要精心设计。

在实际开发中,这种计算器设计适用于:

  • 金融计算工具
  • 科学计算软件
  • 教育类应用
  • 工业控制软件

但需要注意:

  • 无法处理超大规模计算
  • 无法处理复杂的函数嵌套
  • 不适合高并发场景

通过本项目,我们不仅掌握了中规模软件开发的技巧,更理解了如何在实际项目中应用这些技术。建议在开发复杂系统时,始终遵循良好的设计原则,注重代码可维护性和扩展性。

最后修改于:2026年09月23日 19:34

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日