文章目录
前言
最近突然想做几个东西玩玩,于是就想到自己做一个简单的计算器玩玩
一、效果预览
这个计算器就实现了一下基本的功能,可以输入整数、小数、百分数,除了基本的加减乘除运算还有取反,还有删除和清除的功能。
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方法,也就那几步:
- 通过getActionCommand获取对应的按钮
- 调用对应按钮的事件
- 检查输入语法是否正确
- 设置显示对应的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接口
CalcFrame和CalcPanel类似,也是一些布局的东西,这边就不赘述了。代码如下:
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编程实在是不熟 大佬们还请轻喷-_-||