用java实现一个简单的计算器

本文详述了使用Java Swing构建一个简易计算器的过程,包括确定需求、设计按钮布局、实现计算逻辑、处理用户输入规则、创建图形界面组件等步骤。通过创建接口和实现类,遵循面向对象原则,确保计算器功能的完整性和准确性。项目采用Maven管理,支持基本运算及百分比、小数点、取反等功能,并具备异常处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

最近突然想做几个东西玩玩,于是就想到自己做一个简单的计算器玩玩


一、效果预览

    这个计算器就实现了一下基本的功能,可以输入整数、小数、百分数,除了基本的加减乘除运算还有取反,还有删除和清除的功能。
git地址:项目地址
效果如图:
在这里插入图片描述

二、确定需求

    写代码之前,先来捋一下我们要做什么,实现哪些功能

1.按钮

    先来确定一下我们需要哪些按钮,首先是基本的数字键(0-9),接下来是小数点(.)、百分号(%)、加减乘除等于(+,-,×,÷,=)、取反(±)、删除(DEL)、清除(AC)

布局如下图:
在这里插入图片描述
好吧,其实跟上面的图没啥区别

2.规则

    用户的输入是随意的,所以需要限制一下用户的输入,不允许其随便输入。规则如下:

  • 禁止键盘输入,表单为只读状态
  • 百分号只能在结尾,允许出现多次。如:1% 1%% 1%% %以此类推
  • 必须是合法的数字,小数点前后边缺失补0 。如:.1->0.1,1.->1.0
  • 已输入运算符,在输入运算符则会替换。如:12+,再输入-,变成12-
  • 取反如果变成负数增加括号。如:12 取反,变成(-12)
  • 点击等号计算结果
  • 点击DEL按钮,删除前一个字符
  • 点击AC清除所有字符

三、环境信息

    这里用是maven项目,使用maven3.5、jdk1.8。pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hqd</groupId>
    <artifactId>simple-calculator</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>simple-calculator</finalName>
        <plugins>
            <!-- maven 打包时跳过测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>com.hqd.calc.core.Calculator</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

四、创建基础类

1、按钮接口

在这里插入图片描述

package com.hqd.calc;

import com.hqd.calc.graphics.ButtonManager;

import java.awt.event.ActionEvent;

/**
 * 按钮接口
 */
public interface CalcButton {
    //获取按钮内容
    String getText();

    //点击按钮触发事件
    String actionPerformed(ActionEvent e, CalcTextField textField, SyntaxRule syntaxRule);

    //设置按钮管理器
    void setButtonManager(ButtonManager bm);
}

2、 计算接口

在这里插入图片描述

package com.hqd.calc;

import com.hqd.calc.exception.CalcException;

import java.math.BigDecimal;

/**
 * 算法接口,用来实现数学表达式计算
 */
@FunctionalInterface
public interface CalcEngine {
    /**
     * 计算结果
     *
     * @param expression
     * @return
     * @throws CalcException
     */
    BigDecimal calcResult(String expression) throws CalcException;
}

3、语法检查接口

在这里插入图片描述

package com.hqd.calc;

import com.hqd.calc.exception.SyntaxException;

import java.util.Collection;

/**
 * 语法检查接口
 */
public interface SyntaxRule {
    /**
     * 检查语法输入
     *
     * @param expression
     * @return
     * @throws SyntaxException
     */
    String checkSyntax(String expression) throws SyntaxException;

    /**
     * 获取输入列表
     *
     * @return
     */
    Collection<String> getExpressions();

    /**
     * 获取数字正则
     *
     * @return
     */
    String getNumberPattern();

    /**
     * 设置数字正则
     *
     * @param numberPattern
     */
    void setNumberPattern(String numberPattern);

    /**
     * 获取运算正则
     *
     * @return
     */
    String getOperatorPattern();

    /**
     * 设置运算正则
     *
     * @param operatorPattern
     */
    void setOperatorPattern(String operatorPattern);

}

4、 表单输入接口

在这里插入图片描述

package com.hqd.calc;

import java.util.Stack;

/**
 * 输入框接口
 */
public interface CalcTextField {
    /**
     * 清除输入
     *
     * @return
     */
    String clear();

    /**
     * 删除输入
     *
     * @return
     */
    String del();

    /**
     * 设置内容
     *
     * @param t
     */
    void setText(String t);

    /**
     * 获取文本
     *
     * @return
     */
    String getText();

    /**
     * 获取历史输入
     *
     * @return
     */
    Stack<String> getHistoryInput();

    /**
     * 添加历史输入
     *
     * @param input
     */
    void addHistoryInput(String input);

    /**
     * 清除历史输入
     */
    void clearHisHistory();

    /**
     * 设置默认显示
     *
     * @param defaultText
     */
    void setDefaultShow(String defaultText);

    /**
     * 获取默认显示
     *
     * @return
     */
    String getDefaultShow();
}

5、 面板接口

在这里插入图片描述

package com.hqd.calc;

import com.hqd.calc.graphics.ButtonManager;

/**
 * 面板接口
 */
public interface CalcPanel {
    /**
     * 添加按钮
     *
     * @param cbs
     */
    void addButtons(CalcButton[] cbs);

    /**
     * 获取按钮管理器
     *
     * @return
     */
    ButtonManager getButtonManager();
}

6、 frame接口

在这里插入图片描述

package com.hqd.calc;

/**
 * 计算器frame接口
 */
public interface CalcFrame {
    /**
     * 添加面板
     *
     * @param cp
     */
    void addPanel(CalcPanel cp);

    /**
     * 显示
     */
    void showFrame();

    /**
     * 添加输入框
     *
     * @param textField
     */
    void setTextField(CalcTextField textField);

    /**
     * 获取输入框
     *
     * @return
     */
    CalcTextField getTextField();
}

7、还有两个异常类,SyntaxException、CalcException

在这里插入图片描述

package com.hqd.calc.exception;

/**
 * 计算错误异常
 */
public class CalcException extends RuntimeException {
    public CalcException() {
        super();
    }

    public CalcException(String message) {
        super(message);
    }

    public CalcException(String message, Throwable cause) {
        super(message, cause);
    }

    public CalcException(Throwable cause) {
        super(cause);
    }
}

在这里插入图片描述

package com.hqd.calc.exception;

/**
 * 输入格式错误异常
 */
public class SyntaxException extends RuntimeException {
    public SyntaxException() {
        super();
    }

    public SyntaxException(String message) {
        super(message);
    }

    public SyntaxException(String message, Throwable cause) {
        super(message, cause);
    }

    public SyntaxException(Throwable cause) {
        super(cause);
    }
}

五、实现

    前边已经定义好了接口,接下来就一一实现下吧。

1、实现SyntaxRule接口

    还记得我们之前定义的规则吗?检查输入这边就用正则表达式来检查了,接下来我们就来实现下吧。代码如下:

package com.hqd.calc.syntax;

import com.hqd.calc.SyntaxRule;
import com.hqd.calc.exception.SyntaxException;
import com.hqd.calc.utils.NumberUtil;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 语法检查默认实现
 */
public class DefaultSyntaxRule implements SyntaxRule {
    //数字正则
    protected String numberPattern = "((0?\\.\\d*%*)|([1-9]\\d*\\.?\\d*%*)|(\\(-([1-9]\\d*)*\\.?\\d*%*\\))|\\d)";
    //操作符正则
    protected String operatorPattern = "([\\+,\\-,\\×,\\÷])";
    protected Pattern np = Pattern.compile(numberPattern);
    protected Pattern op = Pattern.compile(operatorPattern);

    protected List<String> expressionList = new LinkedList<>();
    //是否开启自动修正
    protected boolean autoRevise;

    public DefaultSyntaxRule(boolean autoRevise) {
        this.autoRevise = autoRevise;
    }

    public DefaultSyntaxRule() {
        this(false);
    }

    @Override
    public String checkSyntax(String expression) throws SyntaxException {
        expressionList.clear();

        String reviseStr = expression;
        if (StringUtils.isBlank(reviseStr))
            return reviseStr;

        if (!check(reviseStr)) {
            throw new SyntaxException("表达式有误:" + reviseStr);
        }
        if (autoRevise)
            reviseStr = revise(expression);
        return reviseStr;
    }

    private boolean check(String str) {
        Matcher nm = np.matcher(str);
        Matcher om = op.matcher(str);
        int len = 0;
        int strLength = str.length();
        int index = 0;
        String matchStr = null;
        while (index < strLength && nm.find(index)) {
            //匹配数字
            matchStr = nm.group();
            len += matchStr.length();
            index += matchStr.length();
            //在匹配操作符
            if (index < strLength && om.find(index)) {
                matchStr = om.group();
                index = str.indexOf(matchStr, index);
                len += matchStr.length();
            } else
                break;
        }
        if (len != strLength) {
            return false;
        }
        return true;
    }

    /**
     * 修正小数点,前置缺失则补0,后置缺失则加0
     * eg: .3 -> 0.3,4. -> 4.0
     *
     * @param str
     * @return
     */
    protected String reviseDecimalNumber(String str) {
        if (NumberUtil.isDecimalNumber(str)) {
            int index = str.indexOf(NumberUtil.SPECIAL_NUMBER_DECIMAL);
            int prev = index - 1;
            int next = index + 1;
            if (prev < 0) {
                str = NumberUtil.NUMBER_0 + str;
            } else if (next >= str.length()) {
                str = str + NumberUtil.NUMBER_0;
            }
        }
        return str;
    }

    /**
     * 修正%,将%转成小数
     * eg: 2% -> 0.02 ,2%% -> 0.0002 以此类推
     *
     * @param str
     * @return
     */
    protected String revisePercentNumber(String str) {
        int index = str.indexOf(NumberUtil.SPECIAL_NUMBER_PERCENT);
        if (index != -1) {
            String tmp = str.substring(0, index);
            String percentStr = str.substring(index);
            BigDecimal bd = NumberUtil.createBigDecimal(tmp);
            bd = bd.divide(new BigDecimal(100).pow(percentStr.length()));
            return bd.toString();
        }
        return str;
    }

    /**
     * 修正负数
     * eg: (-9) -> -9
     *
     * @param str
     * @return
     */
    protected String reviseMinusNumber(String str) {
        if (str.startsWith(NumberUtil.SPECIAL_NUMBER_LEFT_BRACKET)) {
            str = NumberUtil.getNumberStr(0, str);
        }
        return str;
    }

    /**
     * 修正输入的数学表达式
     *
     * @param expression
     * @return
     */
    protected String revise(String expression) {

        Matcher nm = np.matcher(expression);
        Matcher om = op.matcher(expression);
        int start = 0;
        StringBuilder sb = new StringBuilder("");
        System.out.println("====================分割开始========================");
        while (nm.find()) {
            String str = nm.group();
            start += str.length();
            str = reviseMinusNumber(str);
            str = reviseDecimalNumber(str);
            str = revisePercentNumber(str);
            sb.append(str);
            expressionList.add(str);
            if (om.find(start)) {
                String operatorStr = om.group();
                expressionList.add(operatorStr);
                sb.append(operatorStr);
                start += operatorStr.length();
            }
        }
        System.out.println(sb.toString());
        System.out.println("====================分割结束========================");
        return sb.toString();
    }

    @Override
    public Collection<String> getExpressions() {
        return expressionList;
    }

    public String getNumberPattern() {
        return numberPattern;
    }

    public void setNumberPattern(String numberPattern) {
        this.numberPattern = numberPattern;
    }

    public String getOperatorPattern() {
        return operatorPattern;
    }

    public void setOperatorPattern(String operatorPattern) {
        this.operatorPattern = operatorPattern;
    }
}


这里实现了接口checkSyntax方法和revise方法,checkSyntax方法最终实现是check方法

  • check方法:因为计算的表达式是“数字+运算符”的形式,所以先用数字正则匹配到数字,再用运算符正则匹配运算符,如果完全匹配则说明是个正确的表达式,反之则表达式有误
  • revise方法:因为之前确定需求时允许用户的一些特殊的输入,比如百分号、小数点等。所以需要把它们转换成正常的数字添加到链表中去

2、实现CalcEngine接口

    我们先定义一个抽象类,用于实现一些计算引擎公共的功能,代码如下:

package com.hqd.calc.engine;

import com.hqd.calc.CalcEngine;
import com.hqd.calc.SyntaxRule;
import com.hqd.calc.exception.CalcException;
import com.hqd.calc.syntax.DefaultSyntaxRule;
import com.hqd.calc.utils.OperatorUtil;
import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;

/**
 * 计算引擎的基础类
 */
@Getter
@Setter
public abstract class CalcEngineBase implements CalcEngine {
    protected SyntaxRule syntaxRule = new DefaultSyntaxRule(true);

    @Override
    public BigDecimal calcResult(String expression) throws CalcException {
        String text = syntaxRule.checkSyntax(expression);
        return doCalcResult(text);
    }

    /**
     * 两个数的运算
     *
     * @param num1
     * @param num2
     * @param operator
     * @return
     */
    protected BigDecimal calculate(BigDecimal num1, BigDecimal num2, String operator) {
        BigDecimal result = new BigDecimal(0);
        switch (operator) {
            case OperatorUtil.OPERATOR_ADD:
                result = num1.add(num2);
                break;
            case OperatorUtil.OPERATOR_SUB:
                result = num2.subtract(num1);
                break;
            case OperatorUtil.OPERATOR_MUL:
                result = num1.multiply(num2);
                break;
            case OperatorUtil.OPERATOR_DIV:
                result = num2.divide(num1, 10, BigDecimal.ROUND_HALF_UP);
                break;
            default:
                break;
        }
        return result;
    }


    protected abstract BigDecimal doCalcResult(String expression) throws CalcException;

    /**
     * 运算等级
     *
     * @param operator
     * @return
     */
    protected int operatorGrade(char operator) {
        return operatorGrade(String.valueOf(operator));
    }

    /**
     * 运算符优先级,数值越大优先级越高
     *
     * @param operator
     * @return
     */
    protected int operatorGrade(String operator) {
        if (OperatorUtil.OPERATOR_MUL.equals(operator) || OperatorUtil.OPERATOR_DIV.equals(operator)) {
            return 1;
        } else if (OperatorUtil.OPERATOR_ADD.equals(operator) || OperatorUtil.OPERATOR_SUB.equals(operator)) {
            return 0;
        } else if (OperatorUtil.OPERATOR_LEFT_BRACKET.equals(operator) || OperatorUtil.OPERATOR_RIGHT_BRACKET.equals(operator)) {
            return 2;
        } else {
            return 3;
        }
    }
}



这里主要就是calcResult方法了,计算之前先检查修正语法,真正的实现由子类具体实现doCalcResult方法

    接下来再来看下具体实现,代码如下:

package com.hqd.calc.engine;

import com.hqd.calc.exception.CalcException;
import com.hqd.calc.utils.NumberUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Stack;

/**
 * 逆波兰算法
 */
public class RPNCalcEngine extends CalcEngineBase {

    @Override
    protected BigDecimal doCalcResult(String expression) throws CalcException {
        String suffixExpression = infix2Suffix();
        System.out.println(suffixExpression);
        return suffixCalc(suffixExpression);
    }

    /**
     * 后缀表达式计算
     *
     * @param suffixCalcStr
     * @return
     * @throws CalcException
     */
    private BigDecimal suffixCalc(String suffixCalcStr) throws CalcException {
        Stack<BigDecimal> numberStack = new Stack<>();
        String[] strs = suffixCalcStr.split(" ");
        for (int i = 0; i < strs.length; i++) {
            //数字,入数栈
            if (NumberUtil.isNumber(strs[i])) {
                numberStack.push(new BigDecimal(strs[i]));
            } else {
                //数栈有超过两个数,计算
                if (numberStack.size() >= 2) {
                    BigDecimal num1 = numberStack.pop();
                    BigDecimal num2 = numberStack.pop();
                    numberStack.push(calculate(num1, num2, strs[i]));
                }

            }
        }
        if (numberStack.size() != 1)
            throw new CalcException("运算异常");
        BigDecimal bg = numberStack.pop();
        return bg.stripTrailingZeros();
    }

    /**
     * 中缀表达式转后缀表达式
     *
     * @return
     */
    private String infix2Suffix() {
        Collection<String> els = syntaxRule.getExpressions();
        StringBuilder sb = new StringBuilder("");
        Stack<String> operatorStack = new Stack<>();
        Stack<String> numberStack = new Stack<>();
        for (String str : els) {
            //数字入数栈
            if (NumberUtil.isNumber(str)) {
                numberStack.push(str);
            } else {
                //操作栈为空或者为(,直接入操作栈,
                if (NumberUtil.isLeftBracket(str) || operatorStack.isEmpty()) {
                    operatorStack.push(str);
                } else if (NumberUtil.isRightBracket(str)) {//为),操作栈出栈并压入数栈,直到遇见(
                    String operator = operatorStack.pop();
                    //遇到(,或者操作栈为空
                    while (!NumberUtil.isLeftBracket(operator) && !operatorStack.isEmpty()) {
                        numberStack.push(operator);
                        operator = operatorStack.pop();
                    }
                } else {
                    //其他符号,比较操作栈顶运算符优先级
                    while (!operatorStack.isEmpty() && !NumberUtil.isLeftBracket(operatorStack.peek())
                            && operatorGrade(str) <= operatorGrade(operatorStack.peek())) {
                        numberStack.push(operatorStack.pop());
                    }
                    operatorStack.push(str);
                }
            }
        }
        //将剩余的操作符弹出
        while (!operatorStack.isEmpty()) {
            numberStack.push(operatorStack.pop());
        }
        //拼接结果
        while (!numberStack.isEmpty()) {
            sb.append(numberStack.pop() + " ");
        }
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.lastIndexOf(" "));
        }
        String[] tmps = sb.toString().split(" ");
        ArrayUtils.reverse(tmps);
        return StringUtils.join(tmps, " ");
    }
}


这里主要用了逆波兰表达式,这里就不做详细介绍了。主要就是把中缀表达式转成后缀表达式,方便计算机运算

3、定义按键管理器ButtonManager

    计算器的按钮和显示框需要进行联动,所以定义一个ButtonManager作为媒介,ButtonManager实现ActionListener 接口,代码如下:

package com.hqd.calc.graphics;

import com.hqd.calc.CalcButton;
import com.hqd.calc.CalcTextField;
import com.hqd.calc.SyntaxRule;
import com.hqd.calc.exception.SyntaxException;
import com.hqd.calc.syntax.DefaultSyntaxRule;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 按键管理器
 */
public class ButtonManager implements ActionListener {
    private Map<String, CalcButton> buttonMap = new HashMap<>();
    protected SyntaxRule syntaxRule = new DefaultSyntaxRule();
    private CalcTextField textField;

    public ButtonManager(CalcTextField textField) {
        this.textField = textField;
    }

    /**
     * 按钮事件回调
     *
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        CalcButton cb = buttonMap.get(e.getActionCommand());
        if (cb != null) {
            String text = cb.actionPerformed(e, textField, syntaxRule);
            try {
                text = syntaxRule.checkSyntax(text);
                textField.setText(text);
            } catch (SyntaxException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }

    public void addButton(CalcButton cb) {
        if (cb != null) {
            if (!buttonMap.containsKey(cb.getText())) {
                buttonMap.put(cb.getText(), cb);
            }
        }
    }

    public void removeButton(String text) {
        buttonMap.remove(text);
    }

    public void removeButton(CalcButton cb) {
        if (cb != null)
            removeButton(cb.getText());
    }

    public Collection<CalcButton> getButtons() {
        return buttonMap.values();
    }

    public CalcTextField getTextField() {
        return textField;
    }

    public void setTextField(CalcTextField textField) {
        this.textField = textField;
    }

    public SyntaxRule getSyntaxRule() {
        return syntaxRule;
    }

    public void setSyntaxRule(SyntaxRule syntaxRule) {
        this.syntaxRule = syntaxRule;
    }
}

除了actionPerformed方法,其他方法就是一些get、set方法了。接下来看下actionPerformed方法,也就那几步:

  1. 通过getActionCommand获取对应的按钮
  2. 调用对应按钮的事件
  3. 检查输入语法是否正确
  4. 设置显示对应的text值

4、实现CalcButton接口

    接下来,实现一下CalcButton接口,老样子,按键大多数是只要拼接到现有的text后面就行了,所以在提取一个基础类出来,代码如下:

package com.hqd.calc.graphics.button;

import com.hqd.calc.CalcButton;
import com.hqd.calc.CalcTextField;
import com.hqd.calc.SyntaxRule;
import com.hqd.calc.graphics.ButtonManager;

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

/**
 * 计算器按钮
 */
public class CalculatorButton extends JButton implements CalcButton {
    protected String text;
    protected ButtonManager bm;

    public CalculatorButton(String text) {
        super(text);
        this.text = text;
    }

    @Override
    public String getText() {
        return text;
    }

    @Override
    public String actionPerformed(ActionEvent e, CalcTextField textField, SyntaxRule syntaxRule) {
        if (textField != null)
            return textField.getText() + text;
        return text;
    }

    @Override
    public void setButtonManager(ButtonManager bm) {
        this.bm = bm;
        this.addActionListener(bm);
    }
}

上面是基础的按钮类,就是在原本的text后边拼接上自己的字符串就行了,这里操作按钮(+,-,*,/)不大一样,按照确定需求时候的规则:

已输入运算符,在输入运算符则会替换。如:12+,再输入-,变成12-

所以,再提取一个类出来,代码如下:

package com.hqd.calc.graphics.button;

import com.hqd.calc.CalcTextField;
import com.hqd.calc.SyntaxRule;
import com.hqd.calc.utils.OperatorUtil;

import java.awt.event.ActionEvent;
import java.util.Stack;

public class CalculatorOperatorButton extends CalculatorButton {

    public CalculatorOperatorButton(String text) {
        super(text);
    }

    protected boolean prevIsOperator(CalcTextField textField) {
        Stack<String> stack = textField.getHistoryInput();
        if (!stack.isEmpty()) {
            String prev = stack.peek();
            if (OperatorUtil.isOperator(prev)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String actionPerformed(ActionEvent e, CalcTextField textField, SyntaxRule syntaxRule) {
        if (prevIsOperator(textField)) {
            textField.del();
        }
        return textField.getText() + text;
    }
}

和父类差不多,增加了一个prevIsOperator检查方法

  • prevIsOperator 方法:会检查当前栈顶的输入,如果是运算符,则返回true,反之返回false

接下来就是实现一个个具体的按钮了,数字按钮大多只需要继承CalculatorButton 类就可以了,运算符按钮只需要继承CalculatorOperatorButton 类就行了,这里就不贴出全部代码了,具体可以看下源码。现在我们拿出来两个例子来看下:

    数字0按钮

package com.hqd.calc.graphics.button;

import com.hqd.calc.utils.NumberUtil;

public class Number0Button extends CalculatorButton {
    public Number0Button() {
        super(NumberUtil.NUMBER_0);
    }
}

只要写个构造器就行了

    运算符±按钮

package com.hqd.calc.graphics.button;

import com.hqd.calc.CalcTextField;
import com.hqd.calc.SyntaxRule;
import com.hqd.calc.utils.NumberUtil;
import com.hqd.calc.utils.OperatorUtil;

import java.awt.event.ActionEvent;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 取反运算符
 */
public class PMButton extends CalculatorOperatorButton {

    public PMButton() {
        super(OperatorUtil.PM);
    }

    @Override
    public String actionPerformed(ActionEvent e, CalcTextField textField, SyntaxRule syntaxRule) {
        String expression = textField.getText();
        if (prevIsOperator(textField))
            return expression;
        String regex = "(" + syntaxRule.getNumberPattern() + ")$";
        Pattern pattern = Pattern.compile(regex);
        Matcher m = pattern.matcher(expression);
        String result = "";
        //匹配最后一个数字
        if (m.find()) {
            result = m.group();
        }
        String tmp = expression.substring(0, expression.length() - result.length());
        //如果是负数,去除()和-
        if (result.startsWith(NumberUtil.SPECIAL_NUMBER_LEFT_BRACKET) && result.endsWith(NumberUtil.SPECIAL_NUMBER_RIGHT_BRACKET)) {
            result = result.substring(1, result.length() - 1);
            result = result.replace(NumberUtil.SPECIAL_NUMBER_NEGATIVE, "");
        } else {//正数添加()和-
            result = NumberUtil.SPECIAL_NUMBER_LEFT_BRACKET + NumberUtil.SPECIAL_NUMBER_NEGATIVE + result + NumberUtil.SPECIAL_NUMBER_RIGHT_BRACKET;
        }

        return tmp + result;
    }
}

取反按钮略有不同,所以重写了父类的方法。按照我们之前定义的规则:

取反如果变成负数增加括号。如:12 取反,变成(-12)

取反运算符需要匹配到最后一个数字,12 则变成(-12 ),(-12 )变成12

5、实现CalcTextField接口

    上边我们已经把按钮实现完了,接下来看下显示框,代码如下:

package com.hqd.calc.graphics;

import com.hqd.calc.CalcTextField;

import javax.swing.*;
import java.awt.*;
import java.util.Stack;

public class CalculatorTextField extends JTextField implements CalcTextField {
    protected static final String DEFAULT_SHOW = "";
    protected Stack<String> historyInput = new Stack<>();
    private String defaultShowText = DEFAULT_SHOW;

    public CalculatorTextField() {
        this.init();
    }

    private void init() {
        setHorizontalAlignment(JTextField.RIGHT);
        super.setText(defaultShowText);
        setEnabled(false);
        setFont(new Font("宋体", Font.BOLD, 18));
        setDisabledTextColor(Color.BLACK);
    }

    @Override
    public String clear() {
        String text = getText();
        historyInput.clear();
        setText();
        return text;
    }

    @Override
    public String del() {
        String delVal = null;
        if (!historyInput.isEmpty()) {
            delVal = historyInput.pop();
        }
        setText();
        return delVal;
    }

    @Override
    public String getText() {
        if (historyInput.isEmpty())
            return "";
        return super.getText();
    }

    @Override
    public void setText(String t) {
        clearHisHistory();
        for (int i = 0; i < t.length(); i++) {
            historyInput.push(String.valueOf(t.charAt(i)));
        }
        if (historyInput.isEmpty()) {
            t = defaultShowText;
        }
        super.setText(t);
    }

    private void setText() {
        if (historyInput.isEmpty()) {
            setText(defaultShowText);
        } else {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < historyInput.size(); i++) {
                sb.append(historyInput.elementAt(i));
            }
            setText(sb.toString());
        }
    }

    @Override
    public Stack<String> getHistoryInput() {
        return historyInput;
    }

    @Override
    public void addHistoryInput(String input) {
        historyInput.add(input);
    }

    @Override
    public void clearHisHistory() {
        historyInput.clear();
    }

    @Override
    public String getDefaultShow() {
        return defaultShowText;
    }

    @Override
    public void setDefaultShow(String defaultText) {
        this.defaultShowText = defaultText;
    }
}

主要是setText方法

  • setText方法:如果历史记录为空则显示默认值,不为空则显示历史输入

6、实现CalcPanel接口

    按钮和显示框都实现了,接下来需要实现放置按钮的容器,也就是panel,基本就是一些布局的东西了。代码如下:

package com.hqd.calc.graphics.panel;

import com.hqd.calc.CalcButton;
import com.hqd.calc.CalcPanel;
import com.hqd.calc.CalcTextField;
import com.hqd.calc.graphics.ButtonManager;

import javax.swing.*;
import java.awt.*;

public class CalculatorPanel extends JPanel implements CalcPanel {
    //创建面板容器,指定为表格布局,1*4,水平垂直间距为3
    protected static final LayoutManager DEFAULT_LAYOUT = new GridLayout(1, 4, 3, 3);
    private CalcTextField textField;
    private LayoutManager layoutManager;
    private ButtonManager bm;

    public CalculatorPanel(CalcTextField textField) {
        this(textField, DEFAULT_LAYOUT);
    }

    public CalculatorPanel(CalcTextField textField, LayoutManager layoutManager) {
        this.textField = textField;
        this.layoutManager = layoutManager;
        this.init();
    }


    protected void init() {
        this.bm = new ButtonManager(textField);
        setLayout(this.layoutManager);
    }

    @Override
    public void addButtons(CalcButton[] cbs) {
        for (CalcButton cb : cbs) {
            bm.addButton(cb);
            cb.setButtonManager(bm);
            if (cb instanceof Component)
                this.add((Component) cb);
        }
    }

    @Override
    public ButtonManager getButtonManager() {
        return bm;
    }
}

7、实现CalcFrame接口

    CalcFrameCalcPanel类似,也是一些布局的东西,这边就不赘述了。代码如下:

package com.hqd.calc.graphics;

import com.hqd.calc.CalcFrame;
import com.hqd.calc.CalcPanel;
import com.hqd.calc.CalcTextField;

import javax.swing.*;
import java.awt.*;

public class CalculatorFrame extends JFrame implements CalcFrame {
    public static final int DEFAULT_WIDTH = 340;
    public static final int DEFAULT_HEIGHT = 300;
    public static final int DEFAULT_HGAP = 3;
    public static final int DEFAULT_VGAP = 3;
    protected static final LayoutManager DEFAULT_LAYOUT = new GridLayout(6, 3, DEFAULT_HGAP, DEFAULT_VGAP);
    private String title;
    private int width;
    private int height;
    private Component position;
    private LayoutManager layout;
    private CalcTextField calcTextField;

    public CalculatorFrame(String title) {
        this(title, DEFAULT_WIDTH, DEFAULT_HEIGHT, null, DEFAULT_LAYOUT);
    }

    public CalculatorFrame(String title, int row, int cols) {
        this(title, DEFAULT_WIDTH, DEFAULT_HEIGHT, null, new GridLayout(row, cols, DEFAULT_HGAP, DEFAULT_VGAP));
    }

    public CalculatorFrame(String title, int width, int height, Component position, LayoutManager layout) {
        this.title = title;
        this.width = width;
        this.height = height;
        this.position = position;
        this.layout = layout;
        this.init();
    }

    private void init() {
        setTitle(title);
        setPreferredSize(new Dimension(width, height));
        pack();                                    //设置窗体大小以内容大小决定
        setLocationRelativeTo(position);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(layout);
        setResizable(false);
    }

    public void showFrame() {
        setVisible(true);
    }

    @Override
    public void addPanel(CalcPanel cp) {
        if (cp instanceof Component)
            this.add((Component) cp);
    }

    @Override
    public void setTextField(CalcTextField textField) {
        if (textField instanceof Component) {
            CalcTextField old = this.calcTextField;
            if (old != null) {
                this.remove((Component) old);
            }
            this.calcTextField = textField;
            this.add((Component) this.calcTextField);
        }
    }

    @Override
    public CalcTextField getTextField() {
        return calcTextField;
    }
}

8、实现一个入口类

    现在我们需要的都是实现了,万事俱备,只欠东风。只需要把他们组装起来就可以了,代码如下:

package com.hqd.calc.core;

import com.hqd.calc.CalcButton;
import com.hqd.calc.CalcFrame;
import com.hqd.calc.CalcPanel;
import com.hqd.calc.graphics.CalculatorFrame;
import com.hqd.calc.graphics.CalculatorTextField;
import com.hqd.calc.graphics.button.*;
import com.hqd.calc.graphics.panel.CalculatorPanel;

public class Calculator {
    public static void main(String[] args) {
        CalcFrame cf = new CalculatorFrame("简单计算器");
        cf.setTextField(new CalculatorTextField());
        CalcPanel cp = new CalculatorPanel(cf.getTextField());
        cp.addButtons(new CalcButton[]{new ACButton(), new DELButton(), new PMButton(), new DivButton()});
        CalcPanel cp1 = new CalculatorPanel(cf.getTextField());
        cp1.addButtons(new CalcButton[]{new Number7Button(), new Number8Button(), new Number9Button(), new MulButton()});
        CalcPanel cp2 = new CalculatorPanel(cf.getTextField());
        cp2.addButtons(new CalcButton[]{new Number4Button(), new Number5Button(), new Number6Button(), new SubButton()});
        CalcPanel cp3 = new CalculatorPanel(cf.getTextField());
        cp3.addButtons(new CalcButton[]{new Number1Button(), new Number2Button(), new Number3Button(), new AddButton()});

        CalcPanel cp4 = new CalculatorPanel(cf.getTextField());
        cp4.addButtons(new CalcButton[]{new PercentButton(), new Number0Button(), new DecimalButton(), new EQButton()});

        cf.addPanel(cp);
        cf.addPanel(cp1);
        cf.addPanel(cp2);
        cf.addPanel(cp3);
        cf.addPanel(cp4);

        cf.showFrame();
    }
}

好了,这样子就完成了


总结

    这里闲来无聊,做点小玩意自娱自乐,java的gui编程实在是不熟 大佬们还请轻喷-_-||

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值