基于逆波兰表达式实现图形化混合计算器


1 前言

计算器在现实生活中是很普遍的一种工具,所以很多初学者基本上就会用计算器来作为自己的第一个Java项目,当然对于我来说也并不意外。本文采用逆波兰算法来实现计算器的功能,为有需要的小伙伴详细讲解原理和具体实现。完整代码可以查看GitHub仓库

2 表达式求值

2.1 问题分解

我们把算术表达式输入给计算器程序并得到最终的计算结果,背后应存在三个过程:

  1. 表达式合法性检查
  2. 表达式的转换
  3. 计算结果

因此我们可以先建立Calculator类,并加入合法性检验Check,表达式解析(原表达式转换成逆波兰表达式)Conversion和计算结果的方法Calculated,添加成员变量expression以及构造方法Calculator,用来注入和存放代表算术表达式的字符串。

主要代码框架如下:

public class Calculator {
    //存放算术表达式
    private String expression;
    /**
     * 构造函数
     * @param expression 算术表达式
     */
    public Calculator(String expression) {
        this.expression = expression;
    }
    /**
     * @初始化
     */
    public void init() {
       
    }
    /**
     * @表达式合法性检验
     * @return 是否合法
     */
    public boolean Check() {
        return false or true;
    }
    /**
     * @转化为逆波兰式
     * @return 存储在Stack中的逆波兰表达式
     */
    public Stack<String> Conversion() {
        return stack;
    }
    /**
     * 计算结果
     * @return 表达式计算结果
     */
    public String Calculated() {
        return "0";
    }
}

下面我们逐步分析并实现这些过程。

2.2 表达式合法性检查

(1).合法性检查即对用户输入的表达式进行检验,判断其结构和语义是否存在不合法。例如我们期望的用户输入的合法表达式如下:

1+2
10*20-1 
-2*20+10/0.5 
(10+3)/(20-5)

(2).而不合法的表达式输入会是这样:

10*2/0
(10+30)/(20-5
2+3*
+6*3+5
2*-10-20

(3).诸如此类,我们可以从中总结一些有关表达式合法与否的规则:

  1. 括号必定成对出现.
  2. 除数不能为0(由于这个不好判断,故放在了计算的时候判断).
  3. 运算符不能出现在表达式开始或末尾,也不能出现在左括号的右侧或右括号的左侧.
  4. 运算符不能连续出现.

为了方便运算,我们将在负号前面加上0.

2.2.1 括号必须成对出现

我们先考虑括号必须成对出现的规则。这里我们可以使用多种方法来检验。

  1. 我们可以设置一个计数器且初值为0,在遍历数组时,每当读取到(,计数器加1;读取到),计数器减1。任何时候计数器的值都不能小于0,且遍历结束后计数器的值必须也随即归0,否则代表括号未成对出现,即表达式不合法。
  2. 我们可以使用栈(Stack)来解决该问题。在遍历数组时,每当读取到(,将其压入栈;每当读到),将栈中的(弹出栈。如果当读取到)时栈为空,或遍历结束后栈未空,代表括号未成对出现,即表达式不合法。

由于方法1比较简便,故下面采取方法1的实现:

/**
 * @表达式的合法性检测
 * @return boolean
 */
public boolean Check() {
    //首先在负号前面补0
    for (int i = 0; i < expression.length(); i++) {
        if (expression.charAt(i) == '-' && ((i > 0 && !Character.isDigit(expression.charAt(i - 1))) || i == 0)) {
            StringBuilder exp = new StringBuilder(expression);
            exp.insert(i, "0");
            expression = exp.toString();System.out.println(expression);
        }
    }
    //判断括号是否匹配,'('加1,')'减1
    int backets = 0;
    //从左向右扫描
    for (int i = 0; i < expression.length(); i++) {
        switch (expression.charAt(i)) {
            case '(': ++backets; break; //'('加1
            case ')': --backets; //')'减1
                if (backets < 0) //中间出现'('少于')'的情况
                    return false;
                break;
        }
    }
    if (backets != 0) //如果括号不匹配
        return false;
    return true;
}

代码比较清晰,完全按照方法1的思路实现,并且添加了详细注释,不继续赘述。

2.2.2 运算符出现位置

根据运算符不能出现在表达式开始或末尾,也不能出现在左括号的右侧或右括号的左侧,运算符不能连续出现,其中-是可以出现在首位的。这个我们可以利用下标,在循环遍历的switch分支中便捷地检验上述情况。

/**
 * @表达式的合法性检测
 * @return boolean
 */
public boolean Check() {
    //首先在负号前面补0
    for (int i = 0; i < expression.length(); i++) {
        if (expression.charAt(i) == '-' && ((i > 0 && !Character.isDigit(expression.charAt(i - 1))) || i == 0)) {
            StringBuilder exp = new StringBuilder(expression);
            exp.insert(i, "0");
            expression = exp.toString();System.out.println(expression);
        }
    }
    //判断括号是否匹配,'('加1,')'减1
    int backets = 0;
    //从左向右扫描
    for (int i = 0; i < expression.length(); i++) {
        switch (expression.charAt(i)) {
            case '(': ++backets; break; //'('加1
            case ')': --backets; //')'减1
                if (backets < 0) //中间出现'('少于')'的情况
                    return false;
                break;
            case '/':
            case '*':
            case '+':
                if (i == 0) return false; // '/'、'*'、'+'不能出现首位
                if (i > 0 && (expression.charAt(i - 1) == '/' || expression.charAt(i - 1) == '*' || expression.charAt(i - 1) == '+' || expression.charAt(i - 1) == '-' || expression.charAt(i - 1) == '('))
                    return false; //运算符不能连续出现且前面不能为'('
            case '-':
                if (i == expression.length() - 1) return false; //运算符不能出现在尾部
                if (expression.charAt(i + 1) == '/' || expression.charAt(i + 1) == '*' || expression.charAt(i + 1) == '+' || expression.charAt(i + 1) == '-' || expression.charAt(i + 1) == ')')
                    return false; //运算符不能连续出现且后面不能为')'
        }
    }
    if (backets != 0) //如果括号不匹配
        return false;
    return true;
}

至此,算术表达式合法性检验方法实现完毕。

2.3 表达式的转换

接下来我们来分析如何进行表达式的转换。我们都知道四则运算的法则,先乘除后加减,有括号先算括号里的,有多层括号先算内层括号里的。

但是,我们并不能轻而易举地处理一个字符串类型的算术表达式的运算优先级问题,即便是语言工具已经为我们提供了强大的计算能力。因为计算机并不能理解所谓四则运算优先级诸如此类的概念。此时,我们将引出一把宝刀——逆波兰表达式。
具体的内容可详看:波兰式与逆波兰式的转换和表达式求值.

那么知道了什么是逆波兰表达式,下面就进入正题。

1.先引入存放运算符优先级的Map类型成员变量,并完善构造方法:

private Map<String, Integer> priority = new HashMap<>();
/**
 * @构造方法
 * @param expression
 */
public Calculator(String expression) {
    init();
    this.expression = expression;
}
/**
 * @初始化
 */
public void init() {
    priority.put("/", 1);
    priority.put("*", 1);
    priority.put("-", 0);
    priority.put("+", 0);
    priority.put("(", -1);
    priority.put(")", -1);
    priority.put("#", -2);
}

由以上算法思想我们可以逐步实现Conversion方法来实现普通表达式到逆波兰表达式的转化:

/**
 * @中缀表达式转换为后缀表达式
 * @return Stack
 */
public Stack<String> Conversion() {
    Stack<String> s1 = new Stack<>();
    Stack<String> s2 = new Stack<>();
    s1.push("#"); //将最低优先级的#符号放入S1栈,为了方便统一后续操作
    for (int i = 0; i < expression.length(); i++) { //循环遍历表达式
        switch (expression.charAt(i)) {
            case '(': s1.push("("); break; //读取到左括号,直接压入S1栈
            case ')': //若取出的字符是")",则将距离S1栈栈顶最近的"("之间的运算符,逐个出栈,依次送入S2栈,此时抛弃"("。
                while (!s1.peek().equals("(")) {
                    s2.push(s1.pop());
                }
                s1.pop(); break;
            case '/':
            case '*':
            case '-':
            case '+':
                /*
                * 若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,
                * 如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈,
                * 否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,
                * 最后将该运算符送入S1栈。
                * */
                if (priority.get(expression.charAt(i) + "") > priority.get(s1.peek())) {
                    s1.push(expression.charAt(i) + "");
                }
                else {
                    while (!(priority.get(expression.charAt(i) + "") > priority.get(s1.peek()))) {
                        s2.push(s1.pop());
                    }
                    s1.push(expression.charAt(i) + "");
                }
                break;
            default:
                //若取出的字符是操作数,则分析出完整的运算数
                StringBuilder num = new StringBuilder();
                while (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.') {
                    num.append(expression.charAt(i));
                    if (i + 1 < expression.length() && (Character.isDigit(expression.charAt(i + 1)) || expression.charAt(i + 1) == '.'))
                        ++i;
                    else break;
                }
                //该操作数直接送入S2栈
                s2.push(num.toString()); break;
        }
    }
    //将S1栈内所有运算符(不包括"#"),逐个出栈,依次送入S2栈。
    while (!s1.peek().equals("#"))
        s2.push(s1.pop());
    //由于栈的特性,S2应做一下逆序处理
    Stack<String> stack = new Stack<>();
    while (!s2.empty()) {
        stack.push(s2.pop());
    }
    return stack; //返回S2的逆序栈
}

2.4 计算结果

1.得到逆波兰表达式后,我们就要计算结果了。计算方法上文已经提到,这里方便大家再写一遍:

  • 扫描从左往右进行,如果扫描到操作数,则压进S,如果扫描到操作符,则从S弹出两个操作数进行相应的操作,并将结果压进S(S出2进1),当扫描结束后,S的栈顶就是表达式结果。

2.按照上述算法实现Calculated比较简单,具体代码如下:

/**
 * @逆波兰式的求值
 * @return String
 */
public String Calculated() {
    if (!this.Check()) //合法性检验
        return "Error!"; //不合法返回"Error!"
    Stack<String> stack = Conversion(); //得到逆波兰表达式
    Stack<Double> tmp = new Stack<>(); //声明一个栈
    while (!stack.empty()) {
        String s = stack.pop(); //取出逆波兰中的值
        if (Character.isDigit(s.charAt(0))) //如果是操作数
            tmp.push(Double.parseDouble(s)); //直接进入tmp栈
        else {//如果是运算符,取出两个数进行计算
            double b = tmp.pop();
            double a = tmp.pop();
            switch (s) {
                case "/":
                    if (b == 0.0) //如果除数为0,报错
                        return "Error!";
                    tmp.push(Div(a, b)); break;
                case "*": tmp.push(Mul(a, b)); break;
                case "-": tmp.push(Sub(a, b)); break;
                case "+": tmp.push(Add(a, b)); break;
            }
        }
    }
    return tmp.pop().toString();
}
public double Div(double a, double b) {
    BigDecimal a1 = new BigDecimal(a);
    BigDecimal b1 = new BigDecimal(b);
    return a1.divide(b1, 5, BigDecimal.ROUND_HALF_UP).doubleValue();
}
public double Mul(double a, double b) {
    return a * b;
}
public double Sub(double a, double b) {
    return a - b;
}
public double Add(double a, double b) {
    return a + b;
}

3.Calculator类的使用方法:

public static void main(String[] args) {
    System.out.print("输入算式:");
    Scanner scanner = new Scanner(System.in);
    String express = scanner.nextLine();
    Calculator calculator = new Calculator(express);
    System.out.println(calculator.Calculated());
}

4.输出结果:

输入算式:(1+2)*6+1
19.0

3 图形化设计

这个图形化设计我就不多说了,简要说一下就行了。

首先,我们设计一个图形界面肯定需要下面这几个步骤:

  1. 设置界面
  2. 判断监听器
  3. 进行合理输入

3.1 设置界面

大家根据自己的个人喜好,随意设计。我的大概界面如下图:

在这里插入图片描述
程序中组件介绍:

  1. JLabel #标签
  2. JTextField #文本框
  3. JButton #按钮

界面大家就自己设置就行了,我就不再过多的赘余了,代码中有详细的注释,具体见代码:

public class CalculatorFrame extends JFrame {
    JLabel resultLabel = new JLabel(); //结果存放处
    JTextField inputText = new JTextField("0"); //输入框
    JButton clearEmpty = new JButton("CE"); //清除当前输入按钮
    JButton clear = new JButton("C"); //清除所有输入按钮
    JButton backSpace = new JButton("BackSpace"); //删除按钮
    JButton leftPer = new JButton("("); //左括号按钮
    JButton rightPer = new JButton(")"); //右括号按钮
    JButton divisor = new JButton("/"); //除号按钮
    JButton multiply = new JButton("*"); //乘号按钮
    JButton minus = new JButton("-"); //减号按钮
    JButton plus = new JButton("+"); //加号按钮
    JButton posNeg = new JButton("+/-"); //正负号按钮
    JButton decimalPoint = new JButton("."); //小数点按钮
    JButton radicalSign = new JButton("√"); //开根号按钮
    JButton equal = new JButton("="); //等于号按钮
    JButton[] button =  new JButton[10]; //0~9按钮
    public static void main(String[] args) {
        new CalculatorFrame();
    }
    CalculatorFrame() {
        init();
        setLayout(null);
        setTitle("计算器");
        setResizable(false);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setBounds(400, 100, 365, 420);
        setVisible(true);
    }
    public void init() {
        //字体
        Font font = new Font("Courier new", Font.PLAIN, 20);

        //最终结果标签
        resultLabel.setBounds(20, 20, 310, 25); //设置位置和宽高
        resultLabel.setFont(font); //添加字体样式
        add(resultLabel); //添加到窗体

        //输入框样式
        LineBorder lineBorder = new LineBorder(Color.lightGray, 3, true);
        inputText.setBorder(lineBorder); //添加样式
        inputText.setEditable(false); //文本框是否可编辑
        inputText.setBounds(20, 45, 310, 25); //设置位置和宽高
        inputText.setFont(font); //添加字体样式
        add(inputText); //添加到窗体

        //CE按钮样式
        clearEmpty.setBounds(20, 90, 70, 35);
        clearEmpty.setFont(font);
        add(clearEmpty);

        //C按钮样式
        clear.setBounds(100, 90, 70, 35);
        clear.setFont(font);
        add(clear);

        //BackSpace按钮样式
        backSpace.setBounds(180, 90, 150, 35);
        backSpace.setFont(font);
        add(backSpace);

        //除号按钮样式
        divisor.setBounds(260, 135, 70, 35);
        divisor.setFont(font);
        add(divisor);

        //乘号按钮样式
        multiply.setBounds(260, 180, 70, 35);
        multiply.setFont(font);
        add(multiply);

        //减号按钮样式
        minus.setBounds(260, 225, 70, 35);
        minus.setFont(font);
        add(minus);

        //加号按钮样式
        plus.setBounds(260, 270, 70, 35);
        plus.setFont(font);
        add(plus);
        int i = 1;

        //0号按钮样式
        button[0] = new JButton("0");
        button[0].setBounds(20, 270, 70, 35);
        button[0].setFont(font);
        add(button[0]);

        //正负号按钮样式
        posNeg.setBounds(100, 270, 70, 35);
        posNeg.setFont(font);
        add(posNeg);

        //小数点按钮样式
        decimalPoint.setBounds(180, 270, 70, 35);
        decimalPoint.setFont(font);
        add(decimalPoint);

        //1~9按钮样式
        for (int j = 225; j >= 135; j -= 45) {
            for (int k = 20; k <= 180; k += 80) {
                button[i] = new JButton(Integer.toString(i));
                button[i].setBounds(k, j, 70, 35);
                button[i].setFont(font);
                add(button[i++]);
            }
        }

        //左括号按钮样式
        leftPer.setBounds(20, 315, 70, 35);
        leftPer.setFont(font);
        add(leftPer);

        //右括号按钮样式
        rightPer.setBounds(100, 315, 70, 35);
        rightPer.setFont(font);
        add(rightPer);

        //根号按钮样式
        radicalSign.setBounds(180, 315, 70, 35);
        radicalSign.setFont(font);
        add(radicalSign);

        //等于号按钮样式
        equal.setBounds(260, 315, 70, 35);
        equal.setFont(font);
        add(equal);
    }
}

3.2 判断监听器

3.2.1 监听器

当用户按下一个按钮或者单击某个菜单项时,这些动作就会激发一个相应的事件,该事件就会触发事件源上注册的事件监听器(特殊的Java对象),事件监听器调用相应的事件处理器(事件监听器里的实例方法)来做出相应的响应。

三种方法:

  1. 匿名内部类;
  2. 匿名外部类;
  3. 实现接口;

这里使用第三种方法——自身类实现ActionListener接口,作为事件监听器:

public class CalculatorFrame extends JFrame implements ActionListener {
    JLabel resultLabel = new JLabel(); //结果存放处
    JTextField inputText = new JTextField("0"); //输入框
    JButton clearEmpty = new JButton("CE"); //清除当前输入按钮
    JButton clear = new JButton("C"); //清除所有输入按钮
    JButton backSpace = new JButton("BackSpace"); //删除按钮
    JButton leftPer = new JButton("("); //左括号按钮
    JButton rightPer = new JButton(")"); //右括号按钮
    JButton divisor = new JButton("/"); //除号按钮
    JButton multiply = new JButton("*"); //乘号按钮
    JButton minus = new JButton("-"); //减号按钮
    JButton plus = new JButton("+"); //加号按钮
    JButton posNeg = new JButton("+/-"); //正负号按钮
    JButton decimalPoint = new JButton("."); //小数点按钮
    JButton radicalSign = new JButton("√"); //开根号按钮
    JButton equal = new JButton("="); //等于号按钮
    JButton[] button =  new JButton[10]; //0~9按钮
    StringBuffer s1 = new StringBuffer("0"); //记录运算数字,以及保留结果
    StringBuffer s2 = new StringBuffer(); //记录运算数字,保留上一个输入的数字或运算结果
    boolean operator = false; //判断上次输入的是否为运算符
    boolean start = true; //标记运算开始或结束,保证一次运算之后,第二次进行运算时能同时清空显示界面,即s1为空
    public static void main(String[] args) {
        new CalculatorFrame();
    }
    CalculatorFrame() {
        init();
        setLayout(null);
        setTitle("计算器");
        setResizable(false);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setBounds(400, 100, 365, 420);
        setVisible(true);
    }
    public void init() {
        //字体
        Font font = new Font("Courier new", Font.PLAIN, 20);

        //最终结果标签
        resultLabel.setBounds(20, 20, 310, 25); //设置位置和宽高
        resultLabel.setFont(font); //添加字体样式
        add(resultLabel); //添加到窗体

        //输入框样式
        LineBorder lineBorder = new LineBorder(Color.lightGray, 3, true);
        inputText.setBorder(lineBorder); //添加样式
        inputText.setEditable(false); //文本框是否可编辑
        inputText.setBounds(20, 45, 310, 25); //设置位置和宽高
        inputText.setFont(font); //添加字体样式
        add(inputText); //添加到窗体

        //CE按钮样式
        clearEmpty.setBounds(20, 90, 70, 35);
        clearEmpty.setFont(font);
        add(clearEmpty);
        clearEmpty.addActionListener(this); //设置监听器

        //C按钮样式
        clear.setBounds(100, 90, 70, 35);
        clear.setFont(font);
        add(clear);
        clear.addActionListener(this);

        //BackSpace按钮样式
        backSpace.setBounds(180, 90, 150, 35);
        backSpace.setFont(font);
        add(backSpace);
        backSpace.addActionListener(this);

        //除号按钮样式
        divisor.setBounds(260, 135, 70, 35);
        divisor.setFont(font);
        add(divisor);
        divisor.addActionListener(this);

        //乘号按钮样式
        multiply.setBounds(260, 180, 70, 35);
        multiply.setFont(font);
        add(multiply);
        multiply.addActionListener(this);

        //减号按钮样式
        minus.setBounds(260, 225, 70, 35);
        minus.setFont(font);
        add(minus);
        minus.addActionListener(this);

        //加号按钮样式
        plus.setBounds(260, 270, 70, 35);
        plus.setFont(font);
        add(plus);
        plus.addActionListener(this);
        int i = 1;

        //0号按钮样式
        button[0] = new JButton("0");
        button[0].setBounds(20, 270, 70, 35);
        button[0].setFont(font);
        add(button[0]);
        button[0].addActionListener(this);

        //正负号按钮样式
        posNeg.setBounds(100, 270, 70, 35);
        posNeg.setFont(font);
        add(posNeg);
        posNeg.addActionListener(this);

        //小数点按钮样式
        decimalPoint.setBounds(180, 270, 70, 35);
        decimalPoint.setFont(font);
        add(decimalPoint);
        decimalPoint.addActionListener(this);

        //1~9按钮样式
        for (int j = 225; j >= 135; j -= 45) {
            for (int k = 20; k <= 180; k += 80) {
                button[i] = new JButton(Integer.toString(i));
                button[i].setBounds(k, j, 70, 35);
                button[i].setFont(font);
                button[i].addActionListener(this);
                add(button[i++]);
            }
        }

        //左括号按钮样式
        leftPer.setBounds(20, 315, 70, 35);
        leftPer.setFont(font);
        add(leftPer);
        leftPer.addActionListener(this);

        //右括号按钮样式
        rightPer.setBounds(100, 315, 70, 35);
        rightPer.setFont(font);
        add(rightPer);
        rightPer.addActionListener(this);

        //根号按钮样式
        radicalSign.setBounds(180, 315, 70, 35);
        radicalSign.setFont(font);
        add(radicalSign);
        radicalSign.addActionListener(this);

        //等于号按钮样式
        equal.setBounds(260, 315, 70, 35);
        equal.setFont(font);
        add(equal);
        equal.addActionListener(this);
    }
    //监听器
    public void actionPerformed(ActionEvent e) {
        //判断是否为按钮0
        if (e.getSource().equals(button[0])) {
            
        }

        //判断是否为1~9按钮
        for (int i = 1; i <= 9; i++) {
            if (e.getSource().equals(button[i])) {
                
            }
        }

        //判断小数点
        if (e.getSource().equals(decimalPoint)) {
            
        }

        //判断正负号
        if (e.getSource().equals(posNeg)) {
            
        }

        //判断退格Backspace
        if (e.getSource().equals(backSpace)) {
            
        }

        //判断归零CE
        if (e.getSource().equals(clearEmpty)) {
            
        }

        //判断清除C
        if (e.getSource().equals(clear)) {
            
        }

        //判断加号
        if (e.getSource().equals(plus)) {
            
        }

        //判断减号
        if (e.getSource().equals(minus)) {
            
        }

        //判断乘号
        if (e.getSource().equals(multiply)) {
            
        }

        //判断除号
        if (e.getSource().equals(divisor)) {
            
        }

        //判断左括号
        if (e.getSource().equals(leftPer)) {
            
        }

        //判断右括号
        if (e.getSource().equals(rightPer)) {
            
        }

        //判断根号
        if (e.getSource().equals(radicalSign)) {
            
        }
        //判断等号
        if (e.getSource().equals(equal)) {
            
        }
    }
}

3.3 进行合理输入

(1).我们知道, 我们的算术表达式是不能随意输入的,要符合我们正常的算术表达式的逻辑形式。我们知道合法输入应具备下面这些条件:

  1. 数字没有前导0
  2. 小数点只能跟在数字后面且只能出现一次
  3. 左括号前面必须是运算符、左括号或者空
  4. 左括号后面不能直接跟运算符
  5. 右括号前面必须是数字或右括号
  6. 右括号后面不能直接跟数字
  7. 根号下面的结果必须是数字且大于等于0

我们知道,我们计算算术表达式的程序返回的结果有可能是Error!此时运算符、小数点、左右括号、等于号是不能用的。最后,我们可以得到:

public void actionPerformed(ActionEvent e) {
    //判断是否为按钮0
    if (e.getSource().equals(button[0])) {
        if (!s1.toString().equals("0") && !s1.toString().equals("-0") && s1.charAt(s1.length() - 1) != ')') { //')'后面不能直接跟数字
            if (!start || s1.charAt(0) == 'E') { //如果是开始新一轮的运算或是有错误提示
                //s1清零,保证可以重新输入数字
                s1.delete(0, s1.length());
                s2.delete(0, s2.length());
            }
            start = true;
            s1.append("0");
            operator = false;
        }
    }

    //判断是否为1~9按钮
    for (int i = 1; i <= 9; i++) {
        if (e.getSource().equals(button[i])) {
            if (!start || s1.charAt(0) == 'E') { //如果是开始新一轮的运算或是有错误提示
                s1.delete(0, s1.length()); //清空
                s2.delete(0, s2.length());
            }
            start = true;
            if (s1.toString().equals("0") || s1.toString().equals("-0"))
                s1.deleteCharAt(s1.length() - 1); //去除开始的0
            if (s1.length() == 0 || s1.charAt(s1.length() - 1) != ')')
                s1.append(i); //')'后面不能直接跟数字
            operator = false; //输入的不是运算符
        }
    }

    //判断小数点
    if (e.getSource().equals(decimalPoint)) {
        if (!start || s1.charAt(0) == 'E') {
            s1.delete(0, s1.length());
            s1.append("0");
            s2.delete(0, s2.length());
        }
        start = true;
        if (Character.isDigit(s1.charAt(s1.length() - 1)) && s1.indexOf(".") == -1)
            s1.append("."); //如果前面是数字且没出现过小数点
        operator = false; //输入的不是运算符
    }

    //判断正负号
    if (e.getSource().equals(posNeg)) {
        if (s1.charAt(0) == 'E') //如果是错误提示
            return ;
        if (!start) { //如果开始新的运算
            s1.delete(0, s1.length());
            s1.append("0");
            s2.delete(0, s2.length());
        }
        start = true;
        int i;
        for (i = s1.length() - 1; i > 0; --i) //从前往后找,直到遇到不是数字或小数点,在其添加负号
            if (!Character.isDigit(s1.charAt(i)) && s1.charAt(i) != '.')
                break;
        if (Character.isDigit(s1.charAt(i))) //如果都是数字
            s1.insert(i, '-');
        else  if (s1.charAt(i) != '-') //如果此处不是负号,添加负号
            s1.insert(i + 1, '-');
        else s1.deleteCharAt(i); //否则已经有负号,删除
        operator = false; //输入的不是运算符
    }

    //判断退格Backspace
    if (e.getSource().equals(backSpace)) {
        start = true;
        s1.deleteCharAt(s1.length() - 1); //删除最后一个
        if (s1.length() == 0)
            s1.append("0");
        operator = false;
    }

    //判断归零CE
    if (e.getSource().equals(clearEmpty)) {
        //清空当前输入,即s1清零
        //start = true;
        s1.delete(0, s1.length()); //清空
        s1.append("0");
        operator = false;
    }

    //判断清除C
    if (e.getSource().equals(clear)) {
        //清空所有,start标记设为true
        start = true;
        s1.delete(0, s1.length());
        s2.delete(0, s2.length());
        s1.append("0");
        operator = false;
    }

    //判断加号
    if (e.getSource().equals(plus)) {
        if (s1.charAt(0) == 'E') //如果是错误提示,无操作
            return ;
        if (!start)
            s2.delete(0, s2.length());
        //运算符前面不能是做括号和小数点,自动舍去
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        //如果上次是运算符,那么就可以替换上次输入的运算符
        if (operator && s2.length() > 0) {
            s2.deleteCharAt(s2.length() - 1);
            s2.append("+");
        }
        else if (s1.length() > 0) //如果s1还有数据
            s2.append(s1.toString() + "+");
        //s1清零,重新接收下一个数据
        s1.delete(0, s1.length());
        s1.append("0");
        start = true;
        operator = true;
    }

    //判断减号
    if (e.getSource().equals(minus)) {
        if (s1.charAt(0) == 'E')
            return ;
        if (!start)
            s2.delete(0, s2.length());
        //运算符前面不能是做括号和小数点,自动舍去
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        //如果上次是运算符,那么就可以替换上次输入的运算符
        if (operator && s2.length() > 0) {
            s2.deleteCharAt(s2.length() - 1);
            s2.append("-");
        }
        else if (s1.length() > 0) //如果s1还有数据
            s2.append(s1.toString() + "-");
        s1.delete(0, s1.length());
        s1.append("0");
        start = true;
        operator = true;
    }

    //判断乘号
    if (e.getSource().equals(multiply)) {
        if (s1.charAt(0) == 'E')
            return ;
        if (!start)
            s2.delete(0, s2.length());
        //运算符前面不能是做括号和小数点,自动舍去
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        //如果上次是运算符,那么就可以替换上次输入的运算符
        if (operator && s2.length() > 0) {
            s2.deleteCharAt(s2.length() - 1);
            s2.append("*");
        }
        else if (s1.length() > 0) //如果s1还有数据
            s2.append(s1.toString() + "*");
        s1.delete(0, s1.length());
        s1.append("0");
        start = true;
        operator = true;
    }

    //判断除号
    if (e.getSource().equals(divisor)) {
        if (s1.charAt(0) == 'E')
            return ;
        if (!start)
            s2.delete(0, s2.length());
        //运算符前面不能是做括号和小数点,自动舍去
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        //如果上次是运算符,那么就可以替换上次输入的运算符
        if (operator && s2.length() > 0) {
            s2.deleteCharAt(s2.length() - 1);
            s2.append("/");
        }
        else if (s1.length() > 0) //如果s1还有数据
            s2.append(s1.toString() + "/");

        s1.delete(0, s1.length());
        s1.append("0");
        start = true;
        operator = true;
    }

    //判断左括号
    if (e.getSource().equals(leftPer)) {
        if (!start || s1.charAt(0) == 'E') { //开始新一轮的运算或是错误信息
            s1.delete(0, s1.length()); //清空
            s1.append("0");
            s2.delete(0, s2.length());
        }
        start = true;
        //只能在初始状态的时候或者左边为左括号的时候才能添加左括号
        if (s1.toString().equals("0") || s1.toString().equals("-0")) { //如果是初始状态
            s1.deleteCharAt(s1.length() - 1);
            s1.append("(");
        }
        else if (s1.charAt(s1.length() - 1) == '(') //
            s1.append("(");
        operator = false;
    }

    //判断右括号
    if (e.getSource().equals(rightPer)) {
        if (!start || s1.charAt(0) == 'E') {
            s1.delete(0, s1.length());
            s1.append("0");
            s2.delete(0, s2.length());
        }
        start = true;
        //前面必须是数字或右括号才能添加
        if (Character.isDigit(s1.charAt(s1.length() - 1)) || s1.charAt(s1.length() - 1) == ')')
            s1.append(")");
        operator = false;
    }

    //判断根号
    if (e.getSource().equals(radicalSign)) {
        if (s1.charAt(0) == 'E')
            return ;
        start = false;
        //删除无用的数据
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        if (s1.length() == 0)
            s1.append("0");
        String str = new Calculator(s1.toString()).Calculated();//先计算出s1中的表达式
        s2.delete(0, s2.length());
        s2.append(s1);
        s1.delete(0, s1.length());
        s2.insert(0, "√(");
        if (str.equals("Error!") || Double.parseDouble(str) < 0) //如果表达式有错误或结果为负数
            s1.append("Error!");
        else {
            double s = Double.parseDouble(str);
            s1.append(Math.sqrt(s));
        }
        s2.append(")");
        operator = false;
    }

    //判断等号
    if (e.getSource().equals(equal)) {
        if (s1.charAt(0) == 'E')
            return ;
        start = false;
        //删除无用的数据
        while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
            s1.deleteCharAt(s1.length() - 1);
        }
        //如果s1为空,补0
        if (s1.length() == 0)
            s1.append("0");
        String str = new Calculator(s2.append(s1.toString()).toString()).Calculated(); //计算
        if (!str.equals("Error!")) //不是错误信息
            s2.append( "=" + str);
        else s2.append("=");
        s1.delete(0, s1.length());
        s1.append(str);
        operator = false;
    }
    inputText.setText(s1.toString()); //添加到输入框
    resultLabel.setText(s2.toString()); //添加到结果标签
}

上面的程序实现了表达式的判定和计算,为了让计算器有更好的体验,我们可以为计算器加上图形界面,下面的代码主要就是实现了一个图形界面,配合上面的代码,就可以组成一个完美的计算器:

4 源码

4.1 Calculator

这个是计算表达式的源码,可直接运行。

package Calculator;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;

public class Calculator {
    private String expression; //存储表达式
    private Map<String, Integer> priority = new HashMap<>();
    /**
     * @构造方法
     * @param expression
     */
    public Calculator(String expression) {
        init();
        this.expression = expression;
    }
    /**
     * @初始化
     */
    public void init() {
        priority.put("/", 1);
        priority.put("*", 1);
        priority.put("-", 0);
        priority.put("+", 0);
        priority.put("(", -1);
        priority.put(")", -1);
        priority.put("#", -2);
    }
    /**
     * @表达式的合法性检测
     * @return boolean
     */
    public boolean Check() {
        //首先在负号前面补0
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '-' && ((i > 0 && !Character.isDigit(expression.charAt(i - 1))) || i == 0)) {
                StringBuilder exp = new StringBuilder(expression);
                exp.insert(i, "0");
                expression = exp.toString();System.out.println(expression);
            }
        }
        //判断括号是否匹配,'('加1,')'减1
        int backets = 0;
        //从左向右扫描
        for (int i = 0; i < expression.length(); i++) {
            switch (expression.charAt(i)) {
                case '(': ++backets; break; //'('加1
                case ')': --backets; //')'减1
                    if (backets < 0) //中间出现'('少于')'的情况
                        return false;
                    break;
                case '/':
                case '*':
                case '+':
                    if (i == 0) return false; // '/'、'*'、'+'不能出现首位
                    if (i > 0 && (expression.charAt(i - 1) == '/' || expression.charAt(i - 1) == '*' || expression.charAt(i - 1) == '+' || expression.charAt(i - 1) == '-' || expression.charAt(i - 1) == '('))
                        return false; //运算符不能连续出现且前面不能为'('
                case '-':
                    if (i == expression.length() - 1) return false; //运算符不能出现在尾部
                    if (expression.charAt(i + 1) == '/' || expression.charAt(i + 1) == '*' || expression.charAt(i + 1) == '+' || expression.charAt(i + 1) == '-' || expression.charAt(i + 1) == ')')
                        return false; //运算符不能连续出现且后面不能为')'
            }
        }
        if (backets != 0) //如果括号不匹配
            return false;
        return true;
    }

    /**
     * @中缀表达式转换为后缀表达式
     * @return Stack
     */
    public Stack<String> Conversion() {
        Stack<String> s1 = new Stack<>();
        Stack<String> s2 = new Stack<>();
        s1.push("#"); //将最低优先级的#符号放入S1栈,为了方便统一后续操作
        for (int i = 0; i < expression.length(); i++) { //循环遍历表达式
            switch (expression.charAt(i)) {
                case '(': s1.push("("); break; //读取到左括号,直接压入S1栈
                case ')': //若取出的字符是")",则将距离S1栈栈顶最近的"("之间的运算符,逐个出栈,依次送入S2栈,此时抛弃"("。
                    while (!s1.peek().equals("(")) {
                        s2.push(s1.pop());
                    }
                    s1.pop(); break;
                case '/':
                case '*':
                case '-':
                case '+':
                    /*
                    * 若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,
                    * 如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈,
                    * 否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,
                    * 最后将该运算符送入S1栈。
                    * */
                    if (priority.get(expression.charAt(i) + "") > priority.get(s1.peek())) {
                        s1.push(expression.charAt(i) + "");
                    }
                    else {
                        while (!(priority.get(expression.charAt(i) + "") > priority.get(s1.peek()))) {
                            s2.push(s1.pop());
                        }
                        s1.push(expression.charAt(i) + "");
                    }
                    break;
                default:
                    //若取出的字符是操作数,则分析出完整的运算数
                    StringBuilder num = new StringBuilder();
                    while (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.') {
                        num.append(expression.charAt(i));
                        if (i + 1 < expression.length() && (Character.isDigit(expression.charAt(i + 1)) || expression.charAt(i + 1) == '.'))
                            ++i;
                        else break;
                    }
                    //该操作数直接送入S2栈
                    s2.push(num.toString()); break;
            }
        }
        //将S1栈内所有运算符(不包括"#"),逐个出栈,依次送入S2栈。
        while (!s1.peek().equals("#"))
            s2.push(s1.pop());
        //由于栈的特性,S2应做一下逆序处理
        Stack<String> stack = new Stack<>();
        while (!s2.empty()) {
            stack.push(s2.pop());
        }
        return stack; //返回S2的逆序栈
    }

    /**
     * @逆波兰式的求值
     * @return String
     */
    public String Calculated() {
        if (!this.Check()) //合法性检验
            return "Error!"; //不合法返回"Error!"
        Stack<String> stack = Conversion(); //得到逆波兰表达式
        Stack<Double> tmp = new Stack<>(); //声明一个栈
        while (!stack.empty()) {
            String s = stack.pop(); //取出逆波兰中的值
            if (Character.isDigit(s.charAt(0))) //如果是操作数
                tmp.push(Double.parseDouble(s)); //直接进入tmp栈
            else {//如果是运算符,取出两个数进行计算
                double b = tmp.pop();
                double a = tmp.pop();
                switch (s) {
                    case "/":
                        if (b == 0.0) //如果除数为0,报错
                            return "Error!";
                        tmp.push(Div(a, b)); break;
                    case "*": tmp.push(Mul(a, b)); break;
                    case "-": tmp.push(Sub(a, b)); break;
                    case "+": tmp.push(Add(a, b)); break;
                }
            }
        }
        return tmp.pop().toString();
    }
    public double Div(double a, double b) {
        BigDecimal a1 = new BigDecimal(a);
        BigDecimal b1 = new BigDecimal(b);
        return a1.divide(b1, 5, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
    public double Mul(double a, double b) {
        return a * b;
    }
    public double Sub(double a, double b) {
        return a - b;
    }
    public double Add(double a, double b) {
        return a + b;
    }
    public static void main(String[] args) {
        System.out.print("输入算式:");
        Scanner scanner = new Scanner(System.in);
        String express = scanner.nextLine();
        Calculator calculator = new Calculator(express);
        System.out.println(calculator.Calculated());
    }
}

4.2 CalculatorFrame

这个是图形界面源码,里面需要用到Calculator类。

package Calculator;

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

public class CalculatorFrame extends JFrame implements ActionListener {
    JLabel resultLabel = new JLabel(); //结果存放处
    JTextField inputText = new JTextField("0"); //输入框
    JButton clearEmpty = new JButton("CE"); //清除当前输入按钮
    JButton clear = new JButton("C"); //清除所有输入按钮
    JButton backSpace = new JButton("BackSpace"); //删除按钮
    JButton leftPer = new JButton("("); //左括号按钮
    JButton rightPer = new JButton(")"); //右括号按钮
    JButton divisor = new JButton("/"); //除号按钮
    JButton multiply = new JButton("*"); //乘号按钮
    JButton minus = new JButton("-"); //减号按钮
    JButton plus = new JButton("+"); //加号按钮
    JButton posNeg = new JButton("+/-"); //正负号按钮
    JButton decimalPoint = new JButton("."); //小数点按钮
    JButton radicalSign = new JButton("√"); //开根号按钮
    JButton equal = new JButton("="); //等于号按钮
    JButton[] button =  new JButton[10]; //0~9按钮
    StringBuffer s1 = new StringBuffer("0"); //记录运算数字,以及保留结果
    StringBuffer s2 = new StringBuffer(); //记录运算数字,保留上一个输入的数字或运算结果
    boolean operator = false; //判断上次输入的是否为运算符
    boolean start = true; //标记运算开始或结束,保证一次运算之后,第二次进行运算时能同时清空显示界面,即s1为空
    public static void main(String[] args) {
        new CalculatorFrame();
    }
    CalculatorFrame() {
        init();
        setLayout(null);
        setTitle("计算器");
        setResizable(false);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setBounds(400, 100, 365, 420);
        setVisible(true);
    }
    public void init() {
        //字体
        Font font = new Font("Courier new", Font.PLAIN, 20);

        //最终结果标签
        resultLabel.setBounds(20, 20, 310, 25); //设置位置和宽高
        resultLabel.setFont(font); //添加字体样式
        add(resultLabel); //添加到窗体

        //输入框样式
        LineBorder lineBorder = new LineBorder(Color.lightGray, 3, true);
        inputText.setBorder(lineBorder); //添加样式
        inputText.setEditable(false); //文本框是否可编辑
        inputText.setBounds(20, 45, 310, 25); //设置位置和宽高
        inputText.setFont(font); //添加字体样式
        add(inputText); //添加到窗体

        //CE按钮样式
        clearEmpty.setBounds(20, 90, 70, 35);
        clearEmpty.setFont(font);
        add(clearEmpty);
        clearEmpty.addActionListener(this); //设置监听器

        //C按钮样式
        clear.setBounds(100, 90, 70, 35);
        clear.setFont(font);
        add(clear);
        clear.addActionListener(this);

        //BackSpace按钮样式
        backSpace.setBounds(180, 90, 150, 35);
        backSpace.setFont(font);
        add(backSpace);
        backSpace.addActionListener(this);

        //除号按钮样式
        divisor.setBounds(260, 135, 70, 35);
        divisor.setFont(font);
        add(divisor);
        divisor.addActionListener(this);

        //乘号按钮样式
        multiply.setBounds(260, 180, 70, 35);
        multiply.setFont(font);
        add(multiply);
        multiply.addActionListener(this);

        //减号按钮样式
        minus.setBounds(260, 225, 70, 35);
        minus.setFont(font);
        add(minus);
        minus.addActionListener(this);

        //加号按钮样式
        plus.setBounds(260, 270, 70, 35);
        plus.setFont(font);
        add(plus);
        plus.addActionListener(this);
        int i = 1;

        //0号按钮样式
        button[0] = new JButton("0");
        button[0].setBounds(20, 270, 70, 35);
        button[0].setFont(font);
        add(button[0]);
        button[0].addActionListener(this);

        //正负号按钮样式
        posNeg.setBounds(100, 270, 70, 35);
        posNeg.setFont(font);
        add(posNeg);
        posNeg.addActionListener(this);

        //小数点按钮样式
        decimalPoint.setBounds(180, 270, 70, 35);
        decimalPoint.setFont(font);
        add(decimalPoint);
        decimalPoint.addActionListener(this);

        //1~9按钮样式
        for (int j = 225; j >= 135; j -= 45) {
            for (int k = 20; k <= 180; k += 80) {
                button[i] = new JButton(Integer.toString(i));
                button[i].setBounds(k, j, 70, 35);
                button[i].setFont(font);
                button[i].addActionListener(this);
                add(button[i++]);
            }
        }

        //左括号按钮样式
        leftPer.setBounds(20, 315, 70, 35);
        leftPer.setFont(font);
        add(leftPer);
        leftPer.addActionListener(this);

        //右括号按钮样式
        rightPer.setBounds(100, 315, 70, 35);
        rightPer.setFont(font);
        add(rightPer);
        rightPer.addActionListener(this);

        //根号按钮样式
        radicalSign.setBounds(180, 315, 70, 35);
        radicalSign.setFont(font);
        add(radicalSign);
        radicalSign.addActionListener(this);

        //等于号按钮样式
        equal.setBounds(260, 315, 70, 35);
        equal.setFont(font);
        add(equal);
        equal.addActionListener(this);
    }
    //监听器
    public void actionPerformed(ActionEvent e) {
        //判断是否为按钮0
        if (e.getSource().equals(button[0])) {
            if (!s1.toString().equals("0") && !s1.toString().equals("-0") && s1.charAt(s1.length() - 1) != ')') { //')'后面不能直接跟数字
                if (!start || s1.charAt(0) == 'E') { //如果是开始新一轮的运算或是有错误提示
                    //s1清零,保证可以重新输入数字
                    s1.delete(0, s1.length());
                    s2.delete(0, s2.length());
                }
                start = true;
                s1.append("0");
                operator = false;
            }
        }

        //判断是否为1~9按钮
        for (int i = 1; i <= 9; i++) {
            if (e.getSource().equals(button[i])) {
                if (!start || s1.charAt(0) == 'E') { //如果是开始新一轮的运算或是有错误提示
                    s1.delete(0, s1.length()); //清空
                    s2.delete(0, s2.length());
                }
                start = true;
                if (s1.toString().equals("0") || s1.toString().equals("-0"))
                    s1.deleteCharAt(s1.length() - 1); //去除开始的0
                if (s1.length() == 0 || s1.charAt(s1.length() - 1) != ')')
                    s1.append(i); //')'后面不能直接跟数字
                operator = false; //输入的不是运算符
            }
        }

        //判断小数点
        if (e.getSource().equals(decimalPoint)) {
            if (!start || s1.charAt(0) == 'E') {
                s1.delete(0, s1.length());
                s1.append("0");
                s2.delete(0, s2.length());
            }
            start = true;
            if (Character.isDigit(s1.charAt(s1.length() - 1)) && s1.indexOf(".") == -1)
                s1.append("."); //如果前面是数字且没出现过小数点
            operator = false; //输入的不是运算符
        }

        //判断正负号
        if (e.getSource().equals(posNeg)) {
            if (s1.charAt(0) == 'E') //如果是错误提示
                return ;
            if (!start) { //如果开始新的运算
                s1.delete(0, s1.length());
                s1.append("0");
                s2.delete(0, s2.length());
            }
            start = true;
            int i;
            for (i = s1.length() - 1; i > 0; --i) //从前往后找,直到遇到不是数字或小数点,在其添加负号
                if (!Character.isDigit(s1.charAt(i)) && s1.charAt(i) != '.')
                    break;
            if (Character.isDigit(s1.charAt(i))) //如果都是数字
                s1.insert(i, '-');
            else  if (s1.charAt(i) != '-') //如果此处不是负号,添加负号
                s1.insert(i + 1, '-');
            else s1.deleteCharAt(i); //否则已经有负号,删除
            operator = false; //输入的不是运算符
        }

        //判断退格Backspace
        if (e.getSource().equals(backSpace)) {
            //start = true;
            s1.deleteCharAt(s1.length() - 1); //删除最后一个
            if (s1.length() == 0)
                s1.append("0");
            operator = false;
        }

        //判断归零CE
        if (e.getSource().equals(clearEmpty)) {
            //清空当前输入,即s1清零
            //start = true;
            s1.delete(0, s1.length()); //清空
            s1.append("0");
            operator = false;
        }

        //判断清除C
        if (e.getSource().equals(clear)) {
            //清空所有,start标记设为true
            start = true;
            s1.delete(0, s1.length());
            s2.delete(0, s2.length());
            s1.append("0");
            operator = false;
        }

        //判断加号
        if (e.getSource().equals(plus)) {
            if (s1.charAt(0) == 'E') //如果是错误提示,无操作
                return ;
            if (!start)
                s2.delete(0, s2.length());
            //运算符前面不能是做括号和小数点,自动舍去
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            //如果上次是运算符,那么就可以替换上次输入的运算符
            if (operator && s2.length() > 0) {
                s2.deleteCharAt(s2.length() - 1);
                s2.append("+");
            }
            else if (s1.length() > 0) //如果s1还有数据
                s2.append(s1.toString() + "+");
            //s1清零,重新接收下一个数据
            s1.delete(0, s1.length());
            s1.append("0");
            start = true;
            operator = true;
        }

        //判断减号
        if (e.getSource().equals(minus)) {
            if (s1.charAt(0) == 'E')
                return ;
            if (!start)
                s2.delete(0, s2.length());
            //运算符前面不能是做括号和小数点,自动舍去
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            //如果上次是运算符,那么就可以替换上次输入的运算符
            if (operator && s2.length() > 0) {
                s2.deleteCharAt(s2.length() - 1);
                s2.append("-");
            }
            else if (s1.length() > 0) //如果s1还有数据
                s2.append(s1.toString() + "-");
            s1.delete(0, s1.length());
            s1.append("0");
            start = true;
            operator = true;
        }

        //判断乘号
        if (e.getSource().equals(multiply)) {
            if (s1.charAt(0) == 'E')
                return ;
            if (!start)
                s2.delete(0, s2.length());
            //运算符前面不能是做括号和小数点,自动舍去
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            //如果上次是运算符,那么就可以替换上次输入的运算符
            if (operator && s2.length() > 0) {
                s2.deleteCharAt(s2.length() - 1);
                s2.append("*");
            }
            else if (s1.length() > 0) //如果s1还有数据
                s2.append(s1.toString() + "*");
            s1.delete(0, s1.length());
            s1.append("0");
            start = true;
            operator = true;
        }

        //判断除号
        if (e.getSource().equals(divisor)) {
            if (s1.charAt(0) == 'E')
                return ;
            if (!start)
                s2.delete(0, s2.length());
            //运算符前面不能是做括号和小数点,自动舍去
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            //如果上次是运算符,那么就可以替换上次输入的运算符
            if (operator && s2.length() > 0) {
                s2.deleteCharAt(s2.length() - 1);
                s2.append("/");
            }
            else if (s1.length() > 0) //如果s1还有数据
                s2.append(s1.toString() + "/");

            s1.delete(0, s1.length());
            s1.append("0");
            start = true;
            operator = true;
        }

        //判断左括号
        if (e.getSource().equals(leftPer)) {
            if (!start || s1.charAt(0) == 'E') { //开始新一轮的运算或是错误信息
                s1.delete(0, s1.length()); //清空
                s1.append("0");
                s2.delete(0, s2.length());
            }
            start = true;
            //只能在初始状态的时候或者左边为左括号的时候才能添加左括号
            if (s1.toString().equals("0") || s1.toString().equals("-0")) { //如果是初始状态
                s1.deleteCharAt(s1.length() - 1);
                s1.append("(");
            }
            else if (s1.charAt(s1.length() - 1) == '(') //
                s1.append("(");
            operator = false;
        }

        //判断右括号
        if (e.getSource().equals(rightPer)) {
            if (!start || s1.charAt(0) == 'E') {
                s1.delete(0, s1.length());
                s1.append("0");
                s2.delete(0, s2.length());
            }
            start = true;
            //前面必须是数字或右括号才能添加
            if (Character.isDigit(s1.charAt(s1.length() - 1)) || s1.charAt(s1.length() - 1) == ')')
                s1.append(")");
            operator = false;
        }

        //判断根号
        if (e.getSource().equals(radicalSign)) {
            if (s1.charAt(0) == 'E')
                return ;
            start = false;
            //删除无用的数据
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            if (s1.length() == 0)
                s1.append("0");
            String str = new Calculator(s1.toString()).Calculated();//先计算出s1中的表达式
            s2.delete(0, s2.length());
            s2.append(s1);
            s1.delete(0, s1.length());
            s2.insert(0, "√(");
            if (str.equals("Error!") || Double.parseDouble(str) < 0) //如果表达式有错误或结果为负数
                s1.append("Error!");
            else {
                double s = Double.parseDouble(str);
                s1.append(Math.sqrt(s));
            }
            s2.append(")");
            operator = false;
        }

        //判断等号
        if (e.getSource().equals(equal)) {
            if (s1.charAt(0) == 'E')
                return ;
            start = false;
            //删除无用的数据
            while (s1.length() > 0 && (s1.charAt(s1.length() - 1) == '(' || s1.charAt(s1.length() - 1) == '.')) {
                s1.deleteCharAt(s1.length() - 1);
            }
            //如果s1为空,补0
            if (s1.length() == 0)
                s1.append("0");
            String str = new Calculator(s2.append(s1.toString()).toString()).Calculated(); //计算
            if (!str.equals("Error!")) //不是错误信息
                s2.append( "=" + str);
            else s2.append("=");
            s1.delete(0, s1.length());
            s1.append(str);
            operator = false;
        }
        inputText.setText(s1.toString()); //添加到输入框
        resultLabel.setText(s2.toString()); //添加到结果标签
    }
}

5 END

至此,带有图形化的计算器就已经完成了,代码可以查看GitHub仓库

由于本人是一个小白,有错误的地方可以在下方评论区留言或私信我,我们可以相互讨论~~.

原文地址:https://www.cnblogs.com/lzyws739307453/p/12909437.html