Java初学者项目实战:开发一个图形化的计算器应用

Swing是初学者做图形计算器的合理选择,因其JDK自带、无需额外依赖、事件模型直观,且避开JavaFX模块化配置;用GridLayout布局按钮、JTextField作只读显示屏,状态用current/previous/operator三字段管理,事件须在EDT中执行。

为什么 Swing 是初学者做图形计算器的合理选择

因为不需要额外依赖、JDK 自带、事件模型直观,且能避开 JavaFX 的模块化配置和 WebView 等干扰项。Swing 的 JFrameJButtonJTextField 组合足够表达计算器的核心交互——输入、点击、显示结果。

注意:Java 11+ 已将 JavaFX 移出 JDK,而 Swing 仍完整保留在 java.desktop 模块中,无需添加任何 --add-modules 参数就能直接运行。

核心控件布局与事件绑定的关键写法

别用 BorderLayout 堆满整个窗口再硬塞按钮——那样会导致按钮大小失控、无法对齐。推荐用 GridLayout(4, 4) 管理数字和运算符按钮区域,顶部单独放一个 JTextField 作显示屏。

  • JTextField 必须设为 setEditable(false),否则用户键盘输入会破坏计算器状态逻辑
  • 每个 JButtonaddActionListener 中,用 evt.getActionCommand() 获取按钮文本(如 "+""5"),而不是监听 getText() ——后者在按钮文字变更时容易出错
  • 避免在监听器里直接做计算:把解析表达式、执行运算的逻辑抽成独立方法,比如 evaluate(String expr),方便后续替换为更健壮的实现(如双栈算法)

处理“连续点击运算符”和“等号重复触发”的典型 bug

初学者常写出按下 "+" 后再按 "-" 就清空输入的逻辑,但实际应保留前一个操作数,仅更新当前运算符。同理,连续按 "=" 应复用上一次的完整表达式继续计算(如 5 + 3 = → 8,再按 = 应得 16)。

建议用三个字段管理状态:

  • private String currentInput = ""(当前待输入的数字或新操作符)
  • private String previousInput = ""(上一次输入的数字或表达式片段)
  • private String operator = ""(挂起的运算符,如 "+""*"

这样在点击 "=" 时,可拼接成 previousInput + operator + currentInput 再求值,而非简单地调用 eval()

一个可直接运行的最小可工作示例(含关键注释)

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SimpleCalculator extends JFrame { private JTextField display; private String current = ""; private String previous = ""; private String op = "";

public SimpleCalculator() {
    setTitle("简易计算器");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLayout(new BorderLayout());

    display = new JTextField("0");
    display.setFont(new Font("Monospaced", Font.BOLD, 20));
    display.setHorizontalAlignment(JTextField.RIGHT);
    display.setEditable(false);
    add(display, BorderLayout.NORTH);

    JPanel buttonPanel = new JPanel(new GridLayout(4, 4, 5, 5));
    String[] keys = {"7","8","9","/","4","5","6","*","1","2","3","-","0","=","C","+"};
    for (String key : keys) {
        JButton btn = new JButton(key);
        btn.setFont(new Font("SansSerif", Font.PLAIN, 18));
        btn.addActionListener(new CalcListener());
        buttonPanel.add(btn);
    }
    add(buttonPanel, BorderLayout.CENTER);

    pack();
    setLocationRelativeTo(null);
}

private class CalcListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        if (cmd.matches("\\d")) {
            current += cmd;
            display.setText(current);
        } else if (cmd.equals("C")) {
            current = previous = op = "";
            display.setText("0");
        } else if (cmd.equals("=")) {
            if (!previous.isEmpty() && !current.isEmpty() && !op.isEmpty()) {
                double a = Double.parseDouble(previous);
                double b = Double.parseDouble(current);
                double result = switch (op) {
                    case "+" -> a + b;
                    case "-" -> a - b;
                    case "*" -> a * b;
                    case "/" -> b != 0 ? a / b : 0;
                    default -> b;
                };
                current = String.valueOf(result);
                previous = "";
                op = "";
                display.setText(current);
        

} } else { if (!current.isEmpty()) { if (!previous.isEmpty() && op.isEmpty()) { op = cmd; previous = current; current = ""; } else if (!op.isEmpty()) { // 更新运算符,不重新计算 op = cmd; } } } } } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new SimpleCalculator().setVisible(true)); }

}

这个版本不支持小数点、括号或负数,但已覆盖加减乘除、连续运算、清屏等主干流程。真正容易被忽略的是:Swing 的事件必须在 Event Dispatch Thread 中执行,所以 SwingUtilities.invokeLater 不是可选装饰,而是强制要求——漏掉它可能导致界面卡死或组件不响应。