近半个月的时间学习了Java的一些基础语法。为了巩固近段的学习以及增强记忆,手写一个简单的GUI计算器小程序。搜索了一下网上的一些计算器小程序,大部分缺少括号运算、小数点运算、有些仅仅实现了一位数字运算。所以写了一个1.0版的小计算器。话不多说,先上图:
先说一下用到的一些主要的Java基础吧:GUI、事件监听、容器、异常抛出等等吧。
主要的思路:该计算器的实现过程主要是字符串的处理,其中计算过程用了前缀表达式转为后缀表达式,最后再将后缀表达式计算输出结果。
一、GUI界面的设计
界面的设计首先新建一个CalFrame类继承了Frame父类,在Frame中使用了BorderLayout自动布局,又新建了一个TField和一个Panel,Panel中放置了20个Button分别表示数字键,运算符号键等等。其中一些特别的按键做了颜色区别,给每个按键添加事件监听。这是这一部分的设计,这一部分中没有遇到什么大的问题,主要就是页面的调整。其中添加按键时用了一个字符串数组里面存入了每一个按键的名字,一个for循环添加所有的按键,比较方便。
class CalFrame extends Frame {
Panel p1 = new Panel();
String StartString = "请输入...";
TextField TField = new TextField(StartString);
String input = "";
boolean RestFlag = false ;
CalFrame() {
super("My first Calculator By Java");
setBackground(new Color(204,204,255));
setLayout(new BorderLayout());//
setBounds(200,200,400,400);
Panel1Set();
TField.setFont(new Font("宋体",Font.PLAIN,20));
add(TField,BorderLayout.NORTH);
add(p1,BorderLayout.CENTER);
//pack();
this.addWindowListener( new WindowAdapter() { //监听Windows
public void windowClosing(WindowEvent e) {
setVisible(false);
System.exit(0);
}
}
);
setVisible(true);
}
void Panel1Set() {
p1.setBackground(new Color(204,204,255));
p1.setLayout(new GridLayout(4, 5,5,5));//
Button[] button = new Button[20];
String[] buName = {"7","8","9","+","Rest","4","5","6","-","C","1","2","3","*","(","0",".","=","/",")"} ;
ButtonActionMonitor BAM = new ButtonActionMonitor();
for(int i = 0;i<20;i++)
{
button[i] = new Button(buName[i]);
button[i].setBackground(new Color(192,192,192)); //设置按键的背景色
button[i].setFont(new Font("宋体",Font.PLAIN,20));
if ((i+1) % 5 == 4)
button[i].setForeground(Color.BLUE);
button[i].addActionListener(BAM); //添加监听器
p1.add(button[i]);
}
button[17].setForeground(Color.RED);
p1.setVisible(true);
}
二、按键监听器类的实现
按键监听类新建在了CalFrame主类的内部,可以方便的调用主类中的成员变量。监听到即可更改其TFiled的显示。这里用了一个input的字符串,每当监听到某个按键的按下除了等号和Rest复位键,就把对应按键的名字添加到input字符串中去,如果遇到等号按键按下,则处理此时字符串,遇到C键则清楚input中的一个字符,遇到Rest,则复位字符串。下面是该类的代码:
class ButtonActionMonitor implements ActionListener {
public void actionPerformed (ActionEvent e) {
String tmpStr = e.getActionCommand();
System.out.println("a button has been pressed " + tmpStr);
if (tmpStr == "=") {
RestFlag = true ;
Compute(input);
} else if (tmpStr == "Rest") {
input = StartString;
RestFlag = true ;
} else if (tmpStr == "C") { //退格
input = input.substring(0,input.length() - 1);
}else {
if (RestFlag == true) {
input = "";RestFlag = false; //按过等号键后复位操作
}
input += tmpStr;
}
TField.setText(input);
}
}
三、输入字符串的处理
这一部分也是核心代码了,编写过程中也遇到了许多问题。一开始是直接利用输入字符串转为字符数组,然后再进行后缀转前缀等处理,这样就不能实现多位数字以及包含小数点的数字进行运算了。所以后面改为利用一个ArrayList<String> 进行分割处理。分割过程中新建了一个Queue,遇到数字和小数点就先进队列,遇到运算符号或者括号后再将队列中内容输出为一个String,并将该字符串也输出为一个字符串。这样就完成了数字和运算符的分割处理。
ArrayList<String> DataHandle(String input) {
ArrayList<String> c = new ArrayList<String>();
Queue<Character> queue = new LinkedList<Character>();
String numStr = "";
for(int i=0; i<input.length(); i++) {
char tmp = input.charAt(i);
if(Character.isDigit(tmp) || tmp == '.') {
queue.offer(tmp);
} else {
while(queue.size() > 0) {
numStr += queue.poll();
}
if(numStr.length()>0)
c.add(numStr);
c.add(String.valueOf(tmp));
numStr = "";
}
}
while(queue.size() > 0) { //清空队列
numStr += queue.poll();
}
if(numStr.length()>0)
c.add(numStr);
numStr = "";
return c;
}
四、前缀转后缀,以及后缀转为数字运算的处理
这一部分主要参考了大话数据结构一书中的处理办法,网上也有很多讲解,具体不在赘述。其中遇到的一个问题是,对于一个字符串进行switch操作时可以直接匹配字符串,但是在其他匹配中不能使用某个String对象 == “ 需要匹配的字符串”,这种操作是不对的。因为这么操作是匹配的字符串的引用,实际上是两个地址在比较。此处必须使用equals()方法。至于为什么使用Switch语句可以直接匹配,我也不知道为什么。下面直接赋值代码
ArrayList<String> infixToSuffix(ArrayList<String> infix) {
Stack<String> s = new Stack<String>();
ArrayList<String> suffix = new ArrayList<String> ();
for(int i = 0;i < infix.size(); i++) {
String tmp = infix.get(i);
String c = "";
switch (tmp) {
case " ":
break;
case "(":
s.push(tmp); break;
case "+":
case "-":
while(s.size() != 0) {
c = s.pop();
if(c.equals("(")) {
s.push("("); break;
}
suffix.add(c) ;
}
s.push(tmp);
break;
case "*":
case "/":
while(s.size() != 0) {
c = s.pop();
if(c.equals("+") || c.equals("-") || c.equals("(")) {
s.push(c);
break;
} else {
suffix.add(c) ;
}
}
s.push(tmp);
break;
case ")":
while(!s.isEmpty()) {
c = s.pop();
if(c.equals("(")) {
break;
} else {
suffix.add(c) ;
}
}
break;
default:
suffix.add(tmp);
break;
}
}
while(s.size() != 0) {
suffix.add(s.pop());
}
return suffix;
}
Double suffixToNum (ArrayList<String> suffix) {
Stack<Double> stack = new Stack<Double>();
// 使用正则表达式匹配数字
Pattern pattern = Pattern.compile("\\d+||(\\d+\\.\\d+)");
for(int i = 0; i < suffix.size(); i++) {
if (suffix.get(i).equals("")) {
continue;
}
if (pattern.matcher(suffix.get(i)).matches()) {
stack.push(Double.parseDouble(suffix.get(i))); //匹配数字
}
// 如果是运算符,则弹出栈顶的两个数进行计算
else {
double y = stack.pop();
double x = stack.pop();
stack.push(calculate(x, y, suffix.get(i))); // 将运算结果重新压栈
}
}
return stack.pop();
}
Double calculate(double x, double y, String string) {
if(string.trim().equals("+")) {
return x + y;
}
if(string.trim().equals("-")) {
return x - y;
}
if(string.trim().equals("*")) {
return x * y;
}
if(string.trim().equals("/")) {
return x / y;
}
return (double) 0;
}
五、最后的异常处理
因为可能产生不是一个正确的运算表达式就按下等号进行后续运算。此处就会抛出Exception,这里直接catch到进行了一个请输入合法表达式的提示。
void Compute(String Input) {
Double result = 0.0;
try {
result = suffixToNum(infixToSuffix(DataHandle(Input)));
input = result.toString();
}catch (Exception e) {
input = "请输入合法的表达式!";
return ;
}
System.out.println(result);
}
第一次尝试写一个Java小程序。不足之处,多多指教。后续进行学习的过程中,有时间再对该计算器进行改进,可以实现科学计算器、程序员计算器的一些额外功能。后面又尝试着将代码打包成exe文件,然而在没有JVM的电脑环境上还是不能运行,暂时不知道怎么解决。最后放上代码完整版
https://github.com/lxycomeon/Calculator1.0.git。