Java Swing实现一个计算器

一.计算器说明

这里笔者实现的是一个能进行整数,浮点数,正负数复合运算的计算器,每次输入可以输入一个算式,然后点击=后会同时显示算式和结果,设计的界面如下所示:
计算器效果

二.界面设计

  1. 界面布局采用GridBagLayout
  2. 界面顶级容器为JFrame,在顶层容器中添加继承了JPanel的面板类BackgroundPanel,使用这个类可以为面板添加背景图片;
  3. 面板中的控件有两个JTextField控件、20个JButton控件
  4. 在两个JTextField控件中,最上面的一个JTextField控件showexp用来在按下“=”后,展示算式,下面一个JTextField控件show用来在输入时展示算式以及在按下“=”后展示结果,show在没有输入算式时显示“0”;
  5. 20个按钮依次为数值键从0~9、算符键“CE”(清除当前输入数据)、“C”(清空整个算式)、“DEL”(删除一位)、“+”、“-”、“*”、“/”、“+/-”(负号键) 、“.”(小数点)、“=”;
  6. 所有的控件的布局上,第一个JTextField占用一行网格,第二个JTextField占用两行网格,剩下的按钮按每行四个依次排布,JTextField和JButton的上、下、左、右都与周围留有空隙,但是为了美观,第一个JTextField的下边界和第二个JTextField的上边界没有空隙;
  7. 为了进一步美观,两个JTextField控件和20个JButton的显示字符的字体样式设置为Serif、粗体,按钮中字体为蓝色,第一个JTextField和按钮中字体大小为16磅、第二个JTextField中字体大小为40磅,两个JTextField的边框都设置为空边框,字符都是靠右显示。另外,每个按钮都设置为透明效果

三.功控件响应事件处理

1) 首先需要为按钮添加监控器,这样才能响应按键事件,另外我定义了两个字符串exp和outcome(初值都为空串),分别用来存储运算式和运算结果。

2) 对“CE”键的响应
由于要实现清除当前输入数据,在这里我分为两种情况:

  • 第一种是只输入了一个操作数,这种情况下直接设置exp=“0”,并显示在变量名为show的JTextField控件上就可以了;
  • 第二种是当前已经输入了多个操作数,这种情况下只需要找到当前输入的算式中最后一个运算符(+、-、*、/)的位置pos找到,然后以pos+1为提取的末位置,利用字符串提取字串的函数,提取从开始到pos+1的字串存放在exp中并显示在变量名为show的JTextField控件上既可。

3) 对“C”键的响应
“C”键的功能是清空算式内容,因此只需要存储算式的字符串exp设置为“0”即可。

4) 对“del”键的响应
由于按下此键的功能是删除一个数字或算符等,因此我使用字符串提取子串函数substring来提起从字符串开始到字符串倒数第二个字符为止的子串,然后将其显示在JTextField控件中即可,但需要注意的是,需要考虑到空串的情况,当存储算式的字符串exp为空串时,将该字符串设置为“0”然后在JTextField控件上显示0。

5) 对运算符(+、-、*、/)键的响应
由于运算符必须要在数字的后面,因此需要判别当前存储算式的字符串是否为空串,只有不为空串且串的最后一个字符不为运算符和“.”的情况下才能将当前按下的运算符加入到存储算式的字符串exp中去。

6) 对取负数键“+/-”的响应
这里我设置取负数键必须要在输入操作数后按下该键才能起作用,因此需要找到最后一个操作数,所以需要先寻找存储算式的字符串exp中最后一个运算符的位置pos:

  • 若算式中存在运算符且exp的最后一个字符若是数字则exp串为原串分割为位置为0~pos+1的子串+“(”+位置从pos+1到字符串末尾的子串+“)”
  • 若算式中不存在运算符,说明算式中只有一个操作数或为空串,当算式是一个操作数时,直接在算式字符串前加上“-”即可。

7) 对“=”键的响应
当按下等号按键时,在算式后加上“#”,然后利用中缀式求值来计算结果,并将double类型的结果转化为字符串并被outcome引用,在获取到outcome后,需要将算式字符串exp末尾的“#”去掉,然后将算式显示在第一个JTextField控件中,并将结果显示在第二个JTextField控件之中,最后为了下次计算,需要将exp串置为空串。

8) 对“.”键的响应
小数点必须要在数字的后面,且两个小数点间不能都是数字,即不能出现2.33.3这种情况,因此,我先在算式中寻找最后一个小数点的位置pos:

  • 当算式中存在小数点时,在算式最后一个字符为数字且算式从pos+1开始到末尾的子串不都是数字的情况下,才能将小数点加入到算式中去。
  • 当算式中不存在小数点时,只要算式最后一个字符为数字时即可将小数点加入算式。

9) 对数字按键的响应
对于数字按键,一般是直接将当前按下的数字加入到算式中即可,但需要注意不能在算式为“0”的情况下直接将按下的数字直接加入到算式中去,即不能出现0123这种类似的情况,因此当exp为“0”时先将其置为空串,然后将按下的数字加入。

四.算式求值设计

中缀表达式求值中建立一个算符栈(存放Character类型)和一个操作数栈(存放Double类型)。
然后,定义栈内栈外运算符的优先级,具体见下图所示:
算符优先级

  • 对于操作数入栈的实现我采用了一个特别的思路,我在开始处理中缀式之前利用String的spilt函数和正则式将字符串中的操作数提取出来并存储在一个列表中,因此,当读到一个数字或小数点“.”时则继续往下读直至读到一个算符,这时将对应的已提取出来的操作数入栈即可,这样就不需要对算式中的数字进行处理求操作数;
  • 当读到运算符时,需要判断该运算符与算符栈顶的运算符的优先级进行比较,若栈外运算符的优先级更高则栈外运算符入栈,否则取出算符栈的运算符以及操作数进行运算,并将运算结果入栈;
  • 对于“-”号这个一元运算符的实现,则添加了判断语句,从算符栈中取出的是“-”,则先中操作数栈中取出一个数,然后再判断此时操作数栈是否为空且算符栈的栈顶是否为“(”,若上述条件满足,则将该操作数的相反数入栈。

五.完整源程序

//BackgroundPanel类中的代码
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;

//继承Jpanel类创建一个可以添加背景的面板类
public class BackgroundPanel extends JPanel{
    private Image image = null;  
  
    public BackgroundPanel(Image image) {  
        this.image = image;  
    }  
    // 固定背景图片,允许这个JPanel可以在图片上添加其他组件  
    protected void paintComponent(Graphics g) {  
        g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), this);  
    }  
}
//Calculator类中的代码
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

public class Calculator implements ActionListener {
    private JFrame f;
    private JPanel mp;
    //定义两个文本框用来显示算式和结果
    private JTextField show,showexp;
    //定义数字按钮
    private JButton zero,one,two,three,four,five,six,seven,eight,nine;
    //定义控制按钮
    private JButton CE,C,del,point,neg;
    //定义运算符号按钮
    private JButton div,mul,plus,minus,eql;
    //定义算式的字符串表示
    private String exp,outcome;
    
    public static void main(String[] args) {
        Calculator c=new Calculator();
        c.display();
    }
    //在构造函数中初始化控件和变量
    Calculator()
    {
        f=new JFrame("Calculator");
        Image image=new ImageIcon("C:/Users/12849/Pictures/background/bk1.jpg").getImage(); //添加背景图片
        mp=new BackgroundPanel(image);
        show=new JTextField("0");//创建单行文本控件
        showexp=new JTextField();
        zero=new JButton("0");//创建按钮
        one=new JButton("1");
        two=new JButton("2");
        three=new JButton("3");
        four=new JButton("4");
        five=new JButton("5");
        six=new JButton("6");
        seven=new JButton("7");
        eight=new JButton("8");
        nine=new JButton("9");
        CE=new JButton("CE");
        C=new JButton("C");
        del=new JButton("DEL");
        plus=new JButton("+");
        minus=new JButton("-");
        mul=new JButton("*");
        div=new JButton("/");
        neg=new JButton("+/-");
        point=new JButton(".");
        eql=new JButton("=");
        exp=outcome="";//初始设置存储算式和结果的字符串为空串
    }
    
    public void display()
    {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭就退出程序
        
        //设置显示结果的单行文本框相关属性
        show.setFont(new Font(Font.SERIF, Font.BOLD, 40));//设置字体样式
        show.setBorder(BorderFactory.createEmptyBorder());//设置单行文本控件无边框
        show.setHorizontalAlignment(SwingConstants.RIGHT);//设置文本靠右显示
        show.setEnabled(false);//设置单行文本框不能点击
        showexp.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        showexp.setBorder(BorderFactory.createEmptyBorder());
        showexp.setHorizontalAlignment(SwingConstants.RIGHT);
        showexp.setEnabled(false);
        //设置按钮为透明
        CE.setForeground(Color.BLUE);//设置按钮字体颜色为蓝色
        CE.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置按钮字体样式
        CE.setContentAreaFilled(false);//设置按钮为透明效果
        C.setForeground(Color.BLUE);
        C.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        C.setContentAreaFilled(false);
        div.setForeground(Color.BLUE);
        div.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        div.setContentAreaFilled(false);
        mul.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        mul.setForeground(Color.BLUE);
        mul.setContentAreaFilled(false);
        plus.setForeground(Color.BLUE);
        plus.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        plus.setContentAreaFilled(false);
        minus.setForeground(Color.BLUE);
        minus.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        minus.setContentAreaFilled(false);
        point.setForeground(Color.BLUE);
        point.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        point.setContentAreaFilled(false);
        del.setForeground(Color.BLUE);
        del.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        del.setContentAreaFilled(false);
        eql.setForeground(Color.BLUE);
        eql.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        eql.setContentAreaFilled(false);
        zero.setForeground(Color.BLUE);
        zero.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        zero.setContentAreaFilled(false);
        one.setForeground(Color.BLUE);
        one.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        one.setContentAreaFilled(false);
        two.setForeground(Color.BLUE);
        two.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        two.setContentAreaFilled(false);
        three.setForeground(Color.BLUE);
        three.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        three.setContentAreaFilled(false);
        four.setForeground(Color.BLUE);
        four.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        four.setContentAreaFilled(false);
        five.setForeground(Color.BLUE);
        five.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        five.setContentAreaFilled(false);
        six.setForeground(Color.BLUE);
        six.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        six.setContentAreaFilled(false);
        seven.setForeground(Color.BLUE);
        seven.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        seven.setContentAreaFilled(false);
        eight.setForeground(Color.BLUE);
        eight.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        eight.setContentAreaFilled(false);
        nine.setForeground(Color.BLUE);
        nine.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        nine.setContentAreaFilled(false);
        neg.setForeground(Color.BLUE);
        neg.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        neg.setContentAreaFilled(false);
        
        //为按钮添加监听事件
        CE.addActionListener(this);
        C.addActionListener(this);
        del.addActionListener(this);
        plus.addActionListener(this);
        minus.addActionListener(this);
        mul.addActionListener(this);
        div.addActionListener(this);
        point.addActionListener(this);
        neg.addActionListener(this);
        eql.addActionListener(this);
        zero.addActionListener(this);
        one.addActionListener(this);
        two.addActionListener(this);
        three.addActionListener(this);
        four.addActionListener(this);
        five.addActionListener(this);
        six.addActionListener(this);
        seven.addActionListener(this);
        eight.addActionListener(this);
        nine.addActionListener(this);
        
        //设置网格布袋布局
        GridBagLayout gblayout=new GridBagLayout();
        mp.setLayout(gblayout);
        GridBagConstraints g=new GridBagConstraints();
        g.fill=GridBagConstraints.BOTH;//设置当某个单元格未填满时填满整个空间
        g.weightx=1.0;//设置窗口变大时缩放比例
        g.weighty=1.0;
        g.gridx=0;//定位在第一行第一列
        g.gridy=0;
        g.gridwidth=GridBagConstraints.REMAINDER;//填满整行
        g.gridheight=1;//占一行网格
        g.insets=new Insets(5, 5, 0, 5);//设置该组件与其它组件的距离
        gblayout.setConstraints(showexp, g);//将上述
        g.gridx=0; 
        g.gridy=1;
        g.gridheight=2;
        g.insets=new Insets(0, 5, 5, 5);
        gblayout.setConstraints(show, g);
        
        g.insets=new Insets(5, 5, 5, 5);
        g.gridwidth=1;
        g.gridheight=1;
        
        g.gridy=3;
        g.gridx=0;
        gblayout.setConstraints(CE, g);
        g.gridx=1;
        gblayout.setConstraints(C, g);
        g.gridx=2;
        gblayout.setConstraints(del, g);
        g.gridx=3;
        gblayout.setConstraints(div, g);
        
        g.gridy=4;
        g.gridx=0;
        gblayout.setConstraints(seven, g);
        g.gridx=1;
        gblayout.setConstraints(eight, g);
        g.gridx=2;
        gblayout.setConstraints(nine, g);   
        g.gridx=3;
        gblayout.setConstraints(mul, g);
        
        g.gridy=5;
        g.gridx=0;
        gblayout.setConstraints(four, g);
        g.gridx=1;
        gblayout.setConstraints(five, g);
        g.gridx=2;
        gblayout.setConstraints(six, g);    
        g.gridx=3;
        gblayout.setConstraints(minus, g);
        
        g.gridy=6;
        g.gridx=0;
        gblayout.setConstraints(one, g);
        g.gridx=1;
        gblayout.setConstraints(two, g);
        g.gridx=2;
        gblayout.setConstraints(three, g);  
        g.gridx=3;
        gblayout.setConstraints(plus, g);
        
        g.gridy=7;
        g.gridx=0;
        gblayout.setConstraints(neg, g);
        g.gridx=1;
        gblayout.setConstraints(zero, g);
        g.gridx=2;
        gblayout.setConstraints(point, g);  
        g.gridx=3;
        gblayout.setConstraints(eql, g);
        
        mp.add(showexp);
        mp.add(show);
        mp.add(CE);
        mp.add(C);
        mp.add(del);
        mp.add(div);
        mp.add(seven);
        mp.add(eight);
        mp.add(nine);
        mp.add(mul);
        mp.add(four);
        mp.add(five);
        mp.add(six);
        mp.add(minus);
        mp.add(one);
        mp.add(two);
        mp.add(three);
        mp.add(plus);
        mp.add(neg);
        mp.add(zero);
        mp.add(point);
        mp.add(eql);
        
        f.setContentPane(mp);
        f.setSize(440, 500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        outcome="";
        if((e.getSource())==CE){//清除最后一个输入的操作数
            int pos=posOfLastOperator(exp);//获取最后一个运算符的位置
            if(pos>=0)
                exp=exp.substring(0, pos+1);
            else//只有一个操作数直接清空
                exp="0";
            show.setText(exp);
        }
        else if((e.getSource())==C) {//清空算式
            exp="0";
            show.setText(exp);
        }
        else if((e.getSource())==del) {
            //提取字符串开始到倒数第二个字符
            exp=exp.substring(0, exp.length()-1);
            if(exp.length()==0)//删除全部的输入后设置显示0
                exp="0";
            show.setText(exp);
        }
        else if((e.getSource())==plus||(e.getSource())==minus||(e.getSource())==mul||(e.getSource())==div) {
            if(exp.length()!=0&&(!isOperator(exp.charAt(exp.length()-1))))//确认必须有数字才能输入运算符
                exp+=e.getActionCommand();
            show.setText(exp);
        }
        else if((e.getSource())==neg) {
            int pos=posOfLastOperator(exp);
            if(pos>=0)//算符中存在多个操作数
            {
                if(isDigit(exp.charAt(exp.length()-1)))//只有有数字才能置为负数
                    exp=exp.substring(0, pos+1)+"(-"+exp.substring(pos+1)+")";//设置为负数时加上括号以区分与减号
            }
            else//只有一个操作数
            {   
                if(exp!=""&&isDigit(exp.charAt(exp.length()-1)))
                    exp="-"+exp;
            }
            if(exp=="")//设置当算式为空时显示0
                exp="0";
            show.setText(exp);
        }
        else if((e.getSource())==eql) {
            exp+='#';//算式末尾添加’#’
		   //从算式中拆分出数字
            String []nums=exp.split("[^.0-9]");
            List<Double> numLst = new ArrayList<>();
            for (int i = 0; i < nums.length; i++) {//将每个数字串转化为Double类型
                if (!"".equals(nums[i])) 
                    numLst.add(Double.parseDouble(nums[i]));
            }
            double out=getValueOfMid(exp, numLst);//利用中缀式求值
            outcome=""+out;//将求得的结果转为字符串
            exp=exp.substring(0,exp.length()-1);//去除算式后的’#’
            showexp.setText(exp);//第一个单行文本框展示算式
            show.setText(outcome);//第二个单行文本框显示结果
            exp="";//存储算式的字符串清空
        }
        else if((e.getSource())==point) {
            int pos=exp.lastIndexOf('.');//找到最后一个小数点的位置
            
            if(pos>=0)//前后两个小数点间不能都是数字,即不能2.33时又添加一个小数点变为2.33.
            {
                if(isDigit(exp.charAt(exp.length()-1))&&!isDigitSring(exp.substring(pos+1)))
                    exp+=e.getActionCommand();
            }
            else {//小数点前一个必须是数字
                if(isDigit(exp.charAt(exp.length()-1)))
                    exp+=e.getActionCommand();
            }
            show.setText(exp);
        }
        else {
            if(exp=="0")
                exp="";
            exp+=e.getActionCommand();
            show.setText(exp);
        }
    }
    
    public static boolean isDigit(char ch)//判断一个字符是否为数字
    {
        return (ch >= '0'&&ch <= '9');
    }
    
    public  boolean isDigitSring(String s)//判断一个字符是否都为数字
    {
        for(int i=0;i<s.length();i++)
        {
            if(!isDigit(s.charAt(i)))
                return false;
        }
        return true;
    }
    
    public boolean isOperator(char c)//判断一个字符是否为运算符或’.’
    {
        return (c=='+')||(c=='-')||(c=='*')||(c=='/')||(c=='.');
    }
    
    public int posOfLastOperator(String s)//寻找字符串中最后一个运算符(+,-,*,/)的位置
    {
        for(int i=s.length()-1;i>=0;i--)
        {
            if(s.charAt(i)!='.'&&isOperator(s.charAt(i)))
                return i;
        }
        return -1;//找不到返回-1
    }
    
    public static int isp(char ch)//定义栈中运算符优先级,并将相应算符的优先级返回
    {
        switch (ch)
        {
        case')':return 4; 
        case'*':return 3;
        case'/':return 3; 
        case'+':return 2; 
        case'-':return 2; 
        case'(':return 1; 
        case'#':return 0;
        }
        return -1;
    }
    
    public static int icp(char ch)//定义栈外运算符优先级,并将相应算符的优先级返回
    {
        switch (ch)
        {
        case')':return 1; 
        case'*':return 3; 
        case'/':return 3; 
        case'+':return 2; 
        case'-':return 2;
        case'(':return 4; 
        case'#':return 0;
        }
        return 0;
    }
    
    public static double compute(double a,char ch,double b)//将取出的两个操作数与对应的算符进行计算并返回计算结果
    {
        switch (ch)
        {
        case '+':return a + b; 
        case '-':return a - b; 
        case '*':return a * b; 
        case '/':return a / b; 
        default:break;
        }
        return 0;
    }
    //对输入的算式(中缀式)进行求值
    public static double getValueOfMid(String exp, List<Double> numLst)
    {
        Stack<Character> OPTR = new Stack<>();//定义算符栈
        Stack<Double> OPND = new Stack<>();//定义操作数栈
        double outcome=0;//最终结果
        double a,b;//定义两个操作数
        char sym;//定义运算符
        OPTR.push('#');
        int i=0,j=0;
        while(exp.charAt(i)!='#'||OPTR.peek()!='#')
        {
            if(isDigit(exp.charAt(i)))//遍历到数字时则跳过数字字符串,并将之前划分的double类型数据代替压栈
            {
                while(isDigit(exp.charAt(i))||exp.charAt(i)=='.')
                {
                    i++;
                    if(i==exp.length())
                        break;
                }
                i--;
                OPND.push(numLst.get(j));
                j++;
            }
            else
            {
                sym=OPTR.peek();
                int m=isp(sym);
                int n=icp(exp.charAt(i));
                if(m<n)//比较栈内和栈外运算符的优先级
                    OPTR.push(exp.charAt(i));//栈内算符优先级小于栈外,则栈外运算符压栈
                else//栈内运算符优先级大于栈外运算符优先级
                {
                    sym = OPTR.peek();
                    OPTR.pop();//取出栈内的运算符
                    if (sym != '('&&m == n || m>n)
                    {
                        b = OPND.peek();//从操作数栈取出一个操作数
                        OPND.pop();
                        if (!OPND.empty()&&OPTR.peek()!='(')//当操作数栈不为空且不为’(’时继续取出栈中的数进行运算
                        {
                            a = OPND.peek();
                            OPND.pop();
                            OPND.push(compute(a, sym, b));
                            continue;
                        }
                        else//处理负数
                        {
                            switch (sym)//实现一元运算符的运算
                            {
                            case '+':OPND.push(b); break;
                            case '-':OPND.push(-b);break;
                            }
                            continue;
                        }
                    }
                }
            }
            i++;
        }
        outcome=OPND.peek();
        return outcome;
    }
}

以上便是实现的全部过程及源代码,要是觉得好的话就点个赞或关注一下吧!!!

发布了16 篇原创文章 · 获赞 18 · 访问量 4129

猜你喜欢

转载自blog.csdn.net/qq_42103091/article/details/103065669