第二章 仿Windows计算器

2.1 仿Windows计算器概述
Windows 计算器,是 Windows 操作系统自带计算器,,可以帮助用户完成数据的运算,它可分为“标准型“和“科学型”,本章的仿 Windows 计算器是标准型的 Java 实现,标准型 Windows 计算器实现的主要功能有:四则运算;求倒数;求开方;存储计算结果;读取计算结果;累积计算结果。
在本章中,我们将使用到 JFrame 和 JPanel 两个 Swing 容器,使用到 JTextField 和JButton 两个 Swing 容器,使用 BorderLayout 和 GridLayout 做两个布局器,以及使用到事件、事件监听器和事件适配器等。
实现一个计算器,界面中需要提供各种输入的按钮,再以这些按钮组成计算器的键盘,用户点击键盘输入值后,就可以将其所输入的值显示到一个文本框中,运算后,再将结果显示到文本框中。计算器的最终效果如图 2.1 所示。
这里写图片描述
从图 2.1 中可以看到,我们开发界面的时候,需要提供一个文本框在窗口的最上部,文本框下面再提供各个计算器的按钮。
2.1.1 数学与其它符号介绍
在此计算器中,主要使用的数学运算有加、减、乘、除四则运算,或者对一个正数进行开方,或者对一个非 0 的数学求倒数,使用到的数学符号有:
(1)加、减、乘、除,对应使用的符号是”+”,”-“,”*”,”/”;
(2)开方与倒数,对应使用的符号是”sqrt”,”1/x”;
(3)求结果使用的数学符号是”=”;
(4)”%”号,如果使用此符号,第二个操作数就等于两数相乘再除以 100。
除了用于数学运算的符号,Windows 计算器还提供对计算结果做存储、读取、累加、清除等操作,亦有对数字显示框中的数字做退格操作,还可以清除上次计算结果或者全部结果:
(1)使用符号”MC”,”MR”,”MS”,”M+”代表清除存储结果、读取存储结果、保存存储结果和累加存储结果;
(2)使用“Backspace”符号代表退格;
(3)使用”CE”和”C”代表清除上次计算结果和清除所有计算结果。
四则运算在程序中可以直接使用 Java 运算符实现,实现开方可以调用 Math 类的 sqrt 方法,倒数可以使用 1 来除以原始的数字。当用户需点击“=”的时候,计算器就需要将最终的计算结果显示到文本框中。其他的计算器功能都可以通过计算器内部的程序实现,例如使用某个字符串或者数字来保存相应的结果,如果需要计取、存储、累加或者清除结果,可以通过改变或者读取我们所保存的值来实现。
2.1.2 界面说明
界面中使用的 Swing 组件相对简单,整个大窗口可以看作一个 JFrame 对象,在 JFrame 对象中,存放一个 JPanel 对象,我们需要为这个 JPanel 对象进行布局,将文本框(JTextField 对象)与各个计算器按钮(JButton 对象)添加到这个 JPanel 中。在添加计算器按钮的时候,我们可以使用 GridLayout布局处理器来进行网格状布局,由于各个计算器按钮都是以网格状分布在界面中的,因此使用GridLayout 非常适合。

2.2 流程描述
用户打开计算器后,在没有关闭计算器之前,可以通过鼠标点击”1”到”9”
数字键和点击”+”,”-“,”*”,”/”键去输入要运算结果的算术式,再通过点击
“=”,”sqrt”,”1/x”等键去直接获取计算结果,除外,还可以点击”MC”,”MR”
“MS”,”M+”键去清除、读取、保存、累加计算显示框中显示的数字,还有清除上次结果、清除所有结果、退格等操作。从图 2.2 中可以看出,计算器打开之后就开始监听用户的鼠标动作,如果输入是关于计算结果或者
“MC”,”MR”,”MS”,”M+”,”Backspace”,”CE”,”C”等操作指令,而且没有关闭计算器,就返回计算结果并显示,如果不是,则不计算结果。接下来再继续等待用户的输入。
本章的计算器并没有复杂的流程,只需要简单的操作,返回计算结果等。在实现计算器的过程中,我们需要注意的是,例如已经点击了某个数字,再点击运算符,那么程序需要记录之前选点击的数字,当用户再次点击运算符(非“=”)时,系统就需要将结果显示到文本框中。因此在开发计算器的时候,我们需要注意用户点击的具体顺序。
这里写图片描述

2.3 建立计算器对象
实现一个计算器,我们需要建立一系列的对象来实现,例如,计算界面我们要建立一个界面类,还需要建立一个专门负责处理加、减、乘、除的基本计算类,还需要一个负责处理计算功能的业务类。本小节中只讲解创建这三个基本的类,如果在开发的过程发现可以将一些行为或者属性放置到一个新的对象中,那么可以再建立这些对象来完成需要实现的功能或者操作。
本章主要设计四个类来完成计算器的功能,界面类(CalFrame)—主要用来显示计算器界面,功能类(CalService)—主要用于完成计算器中的逻辑功能,计算工具类(MyMath)—此类是工具类,用于处理大型数字的加减乘除,计算器类(Cal)—用于打开计算器,计算器中各个类的关系如图 2.3 所示,从图中可以看出,我们的界面类继承了 java.swing.JFrame 类,计算器类使用了界面类,界面类使用了功能类,功能类使用了 MyMath 工具类,下面章节将对这些计算器的相关类作详细介绍。
这里写图片描述
2.3.1 MyMath工具类
使用 float,double 两种浮点基本类型来进行计算,容易损失精度,所以,我们使用一个自己定义了加,减,乘,除方法的类,此类使用 BigDecimal 来封装基本类型,在不损失精度的同时,也可以进行超大数字的四则运算。为了方便调用,此类的方法全部都是静态方法,可以直接用“类名.方法名”调用,这个类包含以下方法:
(1)static double add( double num1, double num2 ),加法,使用来计算结果的数字是封装后的num1 和 num2,并返回 double 类型。
(2)static double subtract ( double num1, double num2 ),减法,使用来计算结果的数字是封装后的 num1 和 num2,并返回 double 类型。
(3)static double multiply ( double num1, double num2 ),乘法,使用来计算结果的数字是封装后的 num1 和 num2,并返回 double 类型。
(4)static double divide ( double num1, double num2 ),除法,使用来计算结果的数字是封装后的num1 和 num2,并返回 double 类型。
MyMath 类提供了基础的四则运算方法,由于该类中所有的方法都是静态的,因此外界可以直接调用。在实现 MyMath 的过程中需要注意的是,这几个四则运算方法,参数都是 double 类型的,要进行运算的话,需要将 double 类型转换成一个 BigDecimal 对象,我们可以使用以下代码来创建一个BigDecimal 对象:
new BigDecimal(String.valueOf(number));
2.3.2 CalService类
CalService 类主要是用来处理计算器的业务逻辑,用户在操作计算器时,此类将计算结果,并且返回,并且,会记录计算器的状态(用户的上一步操作)。包含以下方法:
(1)String callMethod( String cmd , String text ),调用方法并返回计算结果。
(2)String cal( String text , boolean isPercent ),用来计算加、减、乘、除法,并返回封装成 String类型的结果。参数 text 是显示框中的数字内容,boolean 类型的参数 isPercent 代表是否有”%”运算,如果有,便加上去。
(3)String setReciprocal( String text ),用来计算倒数,并返回封装成 String 类型的结果。
(4)String sqrt( String text ),用来计算开方,并返回封装成 String 类型的结果。
(5)String setOp( String cmd , String text ),设置操作符号。
(6)String setNegative( String text ),设置正负数,当 text 是正数时,返回负数的数字字符串,反之,则返回正数的数字字符串。
(7)String catNum( String cmd, String text ),连接输入的数字,每次点击数字,就把把新加的数字追加到后面,并封装成字符串返回。
(8)String backSpace( String text ),删除最后一个字符,并返回结果。
(9)String mCmd( String cmd, String text ),用来实现”MC”,”MR”,
“MS”,”M+”与存储有关的功能。
(10)String clearAll(),清除所有计算结果。
(11)String clear( String text),清除上次计算结果。
CalService 类中的各个方法都是用于处理计算的逻辑,其中 callMethod 方法可以看作中一个中转的方法,根据参数中的 cmd 值进行分发处理,例如调用该方法时将“CE”字符串作为 cmd,那么该方法就根据这个字符串再调用需要执行“CE”的方法。如果需要做更好的程序解耦,我们可以将这些做成一个状态模式,将各个计算的方法都抽象成一个计算接口,该接口提供一个计算的方法,然后按照具体的情况,为该接口提供不同的实现,例如计算开方、计算倒数等实现,然后向 callMethod 传入不同的实现类,直接调用接口方法。
2.3.3 CalFrame类
CalFrame 类继承 javax.swing.Jframe 类,主要是用于计算器界面的实现,此类中,排版了计算器中各个组件的位置,为组件增加事件监听器,用来监听用户的操作,并做调用相应的方法,主要包含以下方法:
(1)void initialize(),初始化计算器界面。
(2)ActionListener getActionListener(),如果动作监听器为空,则创建一个,并返回,如果不为空,直接返回。
(3)JTextField getTextField(),这个方法初始化输入框。
(4)JButton[] getMButton(),此方法获得计算器的存储操作键。
(5)JButton[] getRButton(),此方法获得计算器的结果操作键。
(6)JButton[] getNButton(),此方法获得计算器的其它操作键。
由于 CalFrame 是界面类,因此所需要进行的业务处理并不多,更多的是监听用户的操作,并进行分发处理。这就有点像 web 应用中的 MVC 模式中的 V(视图),并不处理任务的业务逻辑,主要职责是显示相应的数据。在本章中,CalFrame 包括了一些监听器,监听界面事件并调用相关的业务方法,在实际开发中,我们可以将这些监听器作为 MVC 模式中的 C(控制器)提取到另外的类中。

2.4 MyMath工具类实现
MyMath 是一个工具类,主要用于处理加、减、乘、除四则运算,我们已经确定了实现这四个方法的时候,都使用 BigDecimal 对象进行计算。由于我们定义 MyMath 方法的时候,所有方法的参数都是double类型的,因此我们可以提供一个工具方法来将double 转换成 BigDecimal 类型。
以下代码根据一个 double 类型转换成一个 BigDecimal。

/**
* 为一个 double 类型的数字创建 BigDecimal 对象
* @param number
* @return
*/
private static BigDecimal getBigDecimal(double number) {
return new BigDecimal(number);
}

提供了这个工具方法后,我们可以在其他的计算方法中使用这个工具方法,选择将 double 的参数转换成 BigDecimal 对象,然后再进行具体的运算。
2.4.1 实现四则运算

 //加法
 public static double add(double num1,double num2){
     BigDecimal first=getBigDeciaml(num1);
     BigDecimal second=getBigDeciaml(num2);
     return first.add(second).doubleValue();
 }
 //减法
 public static double subtract(double num1,double num2){
     BigDecimal first=getBigDeciaml(num1);
     BigDecimal second=getBigDeciaml(num2);
     return first.subtract(second).doubleValue();
 }
 //乘法
 public static double multiply(double num1,double num2){
     BigDecimal first=getBigDeciaml(num1);
     BigDecimal second=getBigDeciaml(num2);
     return first.multiply(second).doubleValue();
 }
 //除法
 public static double divide(double num1,double num2){
     BigDecimal first=getBigDeciaml(num1);
     BigDecimal second=getBigDeciaml(num2);
     return first.divide(second).doubleValue();
 }

运算,除本章介绍的方法我,读者可以查阅 Java 的 API 来学习该类的详细使用。

2.5 计算器主界面
这里实现计算器的界面,是用java的Swing实现的,主要用到的类有javax.swing.JFrame(窗口),javax.swing.JButton(按钮),javax.swing.JTextField(输入框),并使用 java.awt.BorderLayout 和java.awt.GridLayout 进行布局。在这里,我们使用“自下而下”的方法去观察此类,先看总体的排版实现,再看各个小组件的实现。为了方便布局,我们按相近的外观把计算器分为四个部分,见图 2.4:
这里写图片描述
2.5.1 初始化界面(initialize()方法)
此类就是一个 JFrame(继承了 javax.swing.JFrame),用来做其它窗口或者组件的父容器,初始化计算器窗口的大概流程:
(1)设置父窗口 JFrame 标题、布局管理器、是否可以改变等属性。
(2)增加输入与计算结果显示框。对应图 2.4 中的左上角那部分。
(3)增加左边存储操作键。
(4)增加结果操作键。
(5)增加数字与其它运算符。
由于按外观相近的方式把组件分成了四部分,就方便程序中对相同属性的组件统一地创建与设置属性,对于界面的布局也更加地直观与方便,观察此图,我们可以使用 BorderLayout 做总体布局,如图2.5 所示。
这里写图片描述
以下代码设置父窗口 JFrame 标题和设置是否可以改变大小的属性。

        //设置窗口的标题
        setTitle("计算器");
        //设置大小
        setSize(350,250);
        //设置为不可改变大小
        setResizable(false);
        //定义窗体布局为边界布局
        setLayout(new BorderLayout());`
增加输入与结果显示的 JTextField 输入框,这里调用本类的 getTextField()方法获取,并把它加入panel 中的 NORTH 位置中:

//增加计算输入框
TextField t=new TextField();

增加左边存储操作键,本类需要通过 getMButton()方法获取一个保存 JButton 对象的数组,getMButton 方法我们将在 2.5.2 中实现。获取数组后,遍历数组,并把数组中的元素加到一个新建的JPanel 中,最后再把这个 JPanel 加到 JFrame 的相应位置:

//增加左边存储操作键
JButton[] mButton = getMButton();
//新建一个 panel,用于放置按钮
JPanel panel1 = new JPanel();
//设置布局管理器
panel1.setLayout( new GridLayout( 5, 1, 0, 5 ) );
//迭代增加按钮
for( JButton b : mButton ) panel1.add(b);

增加结果操作键,这些结果操作键包括:Back,CE,C。通过本类的 getRButton()方法获取一个保存 JButton 对象的数组,获取数组后,遍历数组,并把数组中的元素加到一个新建的 JPanel 中,最后再把这个 JPanel 加到 JFrame 相应的位置,具体实现的代码如下:

//增加结果操作键
JButton [] rButton=getRButton();
//新建一个panel类,用于放置按钮
JPanel panel2=new JPanel();
//设置布局管理器
panel2.setLayout(new GridLayout(1,3));
//迭代增加按钮
for(JButton b : rButton) panel2.add(b);

接下来将其他的按键加入到界面的 JPanel 对象中,这些操作键主要包括数字键和其他的一些运算键,我们同样的通过一个 getNButton 方法来返回这些操作键对应的 JButton 对象,最后将这些 JButton对象加入到相应的 JPanel 中,加入到 JPanel 并设置相应布局的代码如下:

JButton [] nButton=getNButton();
//新建一个panel类,用于放置按钮
JPanel panel3=new JPanel();
//设置布局管理器
panel3.setLayout(new GridLayout(4,5));
//迭代增加按钮
for(JButton b : nButton) panel3.add(b);

最后整体布局:
    //计算器界面
    JPanel panel=new JPanel();
    panel.setLayout( new BorderLayout(1, 5));
    panel.add(panel2,BorderLayout.NORTH);
    panel.add(panel3, BorderLayout.CENTER);
    getContentPane().add(t,BorderLayout.NORTH);
    getContentPane().add(panel1,BorderLayout.WEST);
    getContentPane().add(panel,BorderLayout.CENTER);      
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //让窗体居中显示
    setLocationRelativeTo(null); 

在本小节中,我们通过 getMButton、getRButton 和 getNButton 方法来返回不同的 JButton 数组,然后再对这些数组进行遍历,将每一个 JButton 加入到界面中。这一个返回 JButton 数组的方法并没有实现,下面将介绍如何实现这三个方法。
2.5.2 创建运算键
        运算键主要包括数字键与基本运算键,数字键从 0 到 9,基本运算键包括开方、正负、小数点等键,主要实现计算器界面的 getNButton 方法即可。以下是该方法的实现。

//创建运算按键
private JButton [] getNButton(){
//这个数组保存需要设置为红色的操作符
String [] redButton={“/”,”*”,”-“,”+”,”=”};
JButton [] result=new JButton[nOp.length];
for(int i=0;i

以上代码需要注意的是,我们需要提供一个红色按键的字符串数组,在遍历所有的需要创建的按键数组时,就需要作判断,如果按键数组里面存在红色按键数组的某个元素,就需要调用 JButton 的setForeground 方法来设置该按钮的字体颜色。在代码中我们不能看到该方法帮我们创建了哪些按键,代码中使用了一个 nOp 的字符串数组来保存需要创建的按键,该数组包含的内容如下:

private String[] nOp = { “7”, “8”, “9”, “/”, “sqrt”, “4”, “5”, “6”, “*”,”%”, “1”, “2”, “3”, “-“, “1/x”, “0”, “+/-“, “.”, “+”, “=” };

2.5.3 创建操作按键
        操作按键的创建与运算键的创建基本一致,只是所有的按键的字体都必须是红色的,创建操作按钮,我们需要实现 getMButton 和 getRButton 方法,以下是这两个方法的具体实现。

//创建操作按键
private JButton [] getMButton(){
JButton [] result=new JButton[mOp.length+1];
result[0]=new JButton(“”);
for(int i=0;i

getMButton 创建的是界面左侧的操作键,getRButton 创建的是运作键上面的操作键,getMButton和 getRButton 创建的操作键如下:

//getMButton
private String[] mOp = { “MC”, “MR”, “MS”, “M+” };
//getRButton
private String[] rOp = { “Back”, “CE”, “C” };

创建完界面元素后,我们可以运行计算器,具体的效果如图 2.4 所示。2.5.4 增加事件监听器
         在上一节中,我们注意到程序为 JButton 类型的组件增加了事件监听器,这个事件监听器是用来响应用户的鼠标操作。我们使用 java.awt.event.ActionListener 接口来创建一个事件监听器,主要是实现接口中的 actionPerformed( ActionEvent e )方法,当监听器监听到用户的操作时,会自动调用此方法,并在此方法中处理业务逻辑,再把数据返回显示给用户。见以下代码。

//增加事件监听器
ActionListener actionListener=new ActionListener(){
CalService service=new CalService();
//监听鼠标点击按钮的方法
public void actionPerformed(ActionEvent e){
String cmd=e.getActionCommand();//获取鼠标点击的按钮的内容
String result=null;
try{
//计算操作结果
result=service.callMethod(cmd,t.getText());
}
catch(Exception e1){
System.out.println(e1.getMessage());
}
//处理button标记
if(cmd.indexOf(“MC”)==0){t.setText(“”);}
else if(cmd.indexOf(“M”)==0 && service.store>0){t.setText(“M”);}
if(cmd.equals(“C”)){t.setText(“0”);}
if(cmd.equals(“CE”)){t.setText(“”);}
if(cmd.equals(“Back”)){if(t.getText().equals(“”) || t.getText().equals(“0”))
{t.setText(“0”);}
else
{
String s=t.getText();
t.setText(s.substring(0,s.length()-1));
}}
if(cmd.indexOf(“+”)==0 || cmd.indexOf(“-“)==0 || cmd.indexOf(“*”)==0 || cmd.indexOf(“/”)==0)
{t.setText(t.getText()+cmd);}
//设置计算结果
if(result!=null){t.setText(result);}
}
};

从 上 面 代 码 中 可 以 看 到 , 这 里 是 通 过 实 现 java.awt.event.ActionListener接口中的actionPerformed(ActionEvente)方法去创建一个java.awt.event.ActionListener 类型的内部类,并在actionPerformed 方法中处理业务逻辑。
首先,调用 CalService 实例中的 callMethod 方法去处理计算,并把结果返回。

result = service.callMethod( cmd, textField.getText() );

再设置标志存储结果类型的存储标记,如果是点击“MC”按钮,就把标记设置为空,如果是点击“MS”,“MR”,“M+”,并且存储结果大于 0,就把标记设置为“M”,这里弄不明白的读者,可以先试着使用一下 windows 计算器的这几个按钮,再看这里就很容易理解了。

if( cmd.indexOf(“MC”) == 0 ) {
button.setText(“”);
} else if( cmd.indexOf(“M”) == 0 && service.getStore() > 0 ) {
button.setText(“M”);
}

最后把计算结果设置到结果文本显示框中,显示给使用者。

if( result != null ) {
textField.setText( result );
}

在监听器中,我们调用了 CalServer 的 callMethod 方法来取得操作的结果,换言之,界面中的每次点击都会执行该方法,callMethod 我们并没有提供任何实现,在下一小节,我们将实现该方法。


2.6 计算业务处理
      在 2.3 章节中,我们建立了一个类名为 CalService 的类来处理计算器的计算业务,该类处理了整个应用中的大部分业务,其中包括数字运算,存储运算,操作结果等业务。有四个重要的属性:firstNum代表第一个操作数,secondNum 代表第二个操作数,lastOp 代表上次用户所做的操作, isSecondNum代表是否第二个操作数。
2.6.1 计算四则运算结果
        在使用计算器计算加、减、乘、除法的过程中,正常的情况应该是用户先输入第一个操作数,再点击加、减、乘、除计算符号,再输入第二个操作数,最后点“=”号计算出结果,所以这时用 firstNum去保存用户输入的第一个操作数,secondNum 去保存第二个操作数,lastOp 去保存计算符号或者其它操作,isSecondNum 用来判断用户是在输入第几个操作数。
        在用户输入数字的时候(包括“0123456789.”),首先判断是第一个操作数还是第二个,如果是第一个,就把用户新输入的数字追加到原来数字的后面,并做为结果返回;如果是第二个,直接返回结果,并把 isSecondNum 标志为 false,用户继续输入数字的时候,就把数字追加到原来数字的后面做为结果返回,见以下代码。

//设置文本框中内容
public String catNum(String cmd,String text){
String result=cmd;
//如果目前的text不等于0
if(!text.equals(“0”)){
if(isSecondNum){
//将isSecondNum标记为false
isSecondNum=false;
}
else{
//则返回结果为text加上新点击的数字
result=text+cmd;
}
}
//如果有.开头,则在前面补0
if(result.indexOf(“.”)==0){
result=”0”+result;
}
return result;
}

当用户点击“+-*/”(四则运算)的时候,就把 lastOp 设置为其中一个符号,这个变量用来记录用户正要进行计算的类型,见以下代码。

//设置符号lastOp
public String setOp(String cmd,String text){
//将此操作符设置为上一次的操作
this.lastOp=cmd;
//设置第一个操作数的值
this.firstNum=text;
//将第二个人操作数赋值为空
this.secondNum=null;
//将isSecondNum标记为true
isSecondNum=true;
//返回空值
return null;
}

在上面的代码中,可以看到,除了设置 lastOp 外,还把输入的数字设置给 firstNum,把 secondNum设置为空,并把 isSecondNum 设置为 true,代表下次输入数字时,要清空输入框并重新输入。
最后用户点击“=”号时,就是程序计算出最后结果的时候,此类的 String cal( String text,boolean isPercent )方法实现了此计算,注意到这个方法的第二个参数 isPercent,这是计算器的“%”号操作,如果有这种操作,第二个操作数就等于两数相乘再除以100,请看以下代码。

//四则运算
public String cal(String text,boolean isPercent) throws Exception{
//初始化第二个操作数
this.secondNum=text;
double secondResult=Double.valueOf(secondNum);
//如果除数为0则不处理
if(secondNum.equals(“0”) && this.lastOp.equals(“/”)){return “0”;}
//如果有%操作,则第二个操作数等于两数相乘在除以100
if(isPercent){
secondResult=MyMath.multiply(Double.valueOf(firstNum),MyMath.divide(Double.valueOf(secondNum),100));
}
//四则运算,返回结果赋给第一个操作数
if(this.lastOp.equals(“++”)){
firstNum=String.valueOf(MyMath.add(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“-“)){
firstNum=String.valueOf(MyMath.subtract(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“*”)){
firstNum=String.valueOf(MyMath.multiply(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“/”)){
double num=Double.valueOf(firstNum)/secondResult;
//double num=MyMath.divide(Double.valueOf(firstNum),secondResul);
firstNum=String.valueOf(num);
}
//给第二个操作数重新赋值
secondNum=null;
//把isSecondNum标记为true
this.isSecondNum=true;
return firstNum;
}

上面计算结果中,经历了几个步骤,首先,确定 secondNum 的值,如果 secondNum 为空,secondNum就等于最后输入的数字,如果不为空,则等于原来的值,如果有"%"号操作,则 secondNum再等于两数相乘除以 100 的结果;然后根据 lastOp 的值(+、-、*、/)去调用 MyMath 类中的 addsubtractmultiplydivide 方法,并把返回的结果保存到 firstNum;最后把 secondNum 设置为空,把 firstNum 当做结果返回。
2.6.2 存储操作
         定义一个 double 类型的属性store来充当存储器,在用户点击“MC(清除)”、“M+(累加)”、“MR(读取)”、“MS(保存)”操作时,就调用此方法,再根据得到的字符串去进行清除、累加、读取、保存操作,见以下代码。

public String mCmd(String cmd,String text){
     if(cmd.equals("M+")){
       //如果是M+操作,就把计算结果累积到store中
       store=MyMath.add(store,Double.valueOf(text));
 }
     else if(cmd.equals("MC")){
     //如果是MC操作,则清除store
     store=0;
  }
     else if(cmd.equals("MR")){
     //如果是MR操作,则把store的值读出来
     isSecondNum=true;
     return String.valueOf(store);
 }
     else if(cmd.equals("MS")){
     //如果是MS操作,则把计算结果保存到store
     store=Double.valueOf(text).doubleValue();
 }
     return null;

}

程序中提供了一个 store 的属性用来保存计算结果,当用户点击了“M+”时,就将结果加到 store中,点击了“MC”时,就将 store 设置为 0,点击了“MR”,则将 store 的值读取,点击了“MS”,则将 store 设置为当前的结果。
2.6.3 实现开方、倒数等
         开方与倒数的计算实现都比较简单,开方是直接使用 Math 类的 sqrt 方法去计算接收到的数值,并且返回结果:

public String sqrt(String text){
//将isSecondNum标记为true
this.isSecondNum=true;
//计算结果并返回
return String.valueOf(Math.sqrt(Double.valueOf(text)));
}

倒数是调用 MyMath 的 divide 方法去计算 1 与接收到的数值相除的值。

public String setReciprocal(String text){
//如果text为0,则不求倒数
if(text.equals(“0”)){
return text;
}
else{
//将isSecondNum标记为true
this.isSecondNum=true;
//计算结果并返回
return String.valueOf(1/Double.valueOf(text));
}
}
public String setNegative(String text){
double text1=Double.valueOf(text);
text1=-text1;
return String.valueOf(text1);
}

2.6.4 实现倒退操作
         当我们的程序中得到用户在界面输入的相关数字时,如果用户进行了倒退操作,我们可以将用户输入的数字进行截取,如果接收到的字符串是“0”或者为 null,则不作任何操作,直接返回,否则,我们将使用 String 的 substring 方法进行处理,将输入字符串的最后一位截取。以下方法实现倒退操作。

public String backSpace(String text){
return text.equals(“0”) || text.equals(“”)?”0”:text.substring(0,text.length()-1);
}

2.6.5 清除计算结果
        清除所有计算结果,把 firstNum 与 secondNum 都设置为原始值,并返回 firstNum,在 CalService中提供了一个 clearAll 方法,用于清除所有的计算结果。

public String clearAll(){
//将第一,第二操作数恢复为默认值
this.firstNum=”0”;
this.secondNum=null;
return this.firstNum;
}
public String clear(String text){
text=null;
return text;
}


2.6.6 实现中转方法(callMethod)
        在前面的章节中,我们已经实现了各个方法,例如四则运算、开方、倒数、清除计算等,但是在界面的监听器中,只会调用 CalService 的 callMethod 方法进行运算,因此我们需要对 callMethod 进行相关的实现。

public String callMethod(String cmd,String text) throws Exception{
if(mString.indexOf(cmd)!=-1){
return mCmd(cmd,text);
}
else if(cmd.equals(“C”)){
return clearAll();
}
else if(cmd.equals(“CE”)){
return clear(text);
}
else if(cmd.equals(“BACK”)){
return backSpace(“text”);
}
else if(numString.indexOf(cmd)!=-1){
return catNum(cmd,text);
}
else if(opString.indexOf(cmd)!=-1){
return setOp(cmd,text);
}
else if(cmd.equals(“=”)){
return cal(text,false);
}
else if(cmd.equals(“+/-“)){
return setNegative(text);
}
else if(cmd.equals(“1/x”)){
return setReciprocal(text);
}
else if(cmd.equals(“sqrt”)){
return sqrt(text);
}
else if(cmd.equals(“%”)){
return cal(text,true);
}
else{
return mCmd(cmd,text);
}
}


2.7 总结
     完整代码如下:
    CalFrame类:

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

public class CalFrame extends JFrame {
private static final long serialVersionUID=12L;//版本,必须要写
private String[] nOp={“7”,”8”,”9”,”/”,”sqrt”,”4”,”5”,”6”,”*”,
“%”,”1”,”2”,”3”,”-“,”1/x”,”0”,”+/-“,”.”,”++”,”=”};
private String[] mOp={“MC”,”MR”,”MS”,”M+”};
private String[] rOp = {“Back”,”CE”,”C”};
//定义文本框
TextField t=new TextField();//JTextArea t=new JTextArea();

public CalFrame(){
    //设置窗口的标题
    setTitle("计算器");
    //设置大小
    setSize(350,250);
    //设置为不可改变大小
    setResizable(false);
    //定义窗体布局为边界布局
    setLayout(new BorderLayout());
}
 //初始化界面
 private void initialize(){
     /**
     *增加左边存储操作键
     */
     JButton [] mButton=getMButton();
     //新建一个panel类,用于放置按钮
     JPanel panel1=new JPanel();
     //设置布局管理器
     panel1.setLayout(new GridLayout(5,1));
     //迭代增加按钮
     for(JButton b : mButton) panel1.add(b); 
     /**
      *增加结果操作键
      */
     JButton [] rButton=getRButton();
     //新建一个panel类,用于放置按钮
     JPanel panel2=new JPanel();
     //设置布局管理器
     panel2.setLayout(new GridLayout(1,3));
     //迭代增加按钮
     for(JButton b : rButton) panel2.add(b);
     /**
      *增加数字与其他运算符操作键
      */
     JButton [] nButton=getNButton();
     //新建一个panel类,用于放置按钮
     JPanel panel3=new JPanel();
     //设置布局管理器
     panel3.setLayout(new GridLayout(4,5));
     //迭代增加按钮
     for(JButton b : nButton) panel3.add(b);


     //计算器界面
     JPanel panel=new JPanel();
     panel.setLayout( new BorderLayout(1, 5));
     panel.add(panel2,BorderLayout.NORTH);
     panel.add(panel3, BorderLayout.CENTER);
     getContentPane().add(t,BorderLayout.NORTH);
     getContentPane().add(panel1,BorderLayout.WEST);
     getContentPane().add(panel,BorderLayout.CENTER);      
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//可要可不要
     //让窗体居中显示
     setLocationRelativeTo(null);       
}


     //创建运算按键
     private JButton [] getNButton(){
     //这个数组保存需要设置为红色的操作符
     String [] redButton={"/","*","-","++","="};
     JButton [] result=new JButton[nOp.length];
     for(int i=0;i<this.nOp.length;i++){
     //新建按钮
     JButton b=new JButton(this.nOp[i]);
     //为按钮增加事件
     b.addActionListener(actionListener);
     //对redButton排序,才可以使用binarySearch方法
     Arrays.sort(redButton);
    //如果操作符在redButton出现
     if(Arrays.binarySearch(redButton,nOp[i])>=0){
          b.setForeground(Color.red);
     }
          else{
          b.setForeground(Color.blue);
     }
          result[i]=b;
     }
          return result;
     }

      //创建操作按键
      private  JButton [] getMButton(){
      JButton [] result=new JButton[mOp.length+1];
      result[0]=new JButton("");
      for(int i=0;i<this.mOp.length;i++){
      //新建按钮
      JButton b=new JButton(this.mOp[i]);
      //为按钮增加事件
      b.addActionListener(actionListener);
      //设置按钮颜色
      b.setForeground(Color.red);
      result[i+1]=b;
     }
      return result;
    }

       //创建其他按钮
       private JButton [] getRButton(){
       JButton [] result=new JButton[rOp.length];
       for(int i=0;i<this.rOp.length;i++){
       //新建按钮
       JButton b=new JButton(this.rOp[i]);
       //为按钮增加事件
       b.addActionListener(actionListener);
       //设置按钮颜色
       b.setForeground(Color.red);
       result[i]=b;
    }
        return result;
    }


       //增加事件监听器
       ActionListener actionListener=new ActionListener(){
       CalService service=new CalService();
       //监听鼠标点击按钮的方法
       public void actionPerformed(ActionEvent e){
       String cmd=e.getActionCommand();//获取鼠标点击的按钮的内容
       String result=null;
       try{
            //计算操作结果
            result=service.callMethod(cmd,t.getText());
       }
       catch(Exception e1){
            System.out.println(e1.getMessage());
       }
       //处理button标记
       if(cmd.indexOf("MC")==0){t.setText("");}
       else if(cmd.indexOf("M")==0 && service.store>0){t.setText("M");}
       if(cmd.equals("C")){t.setText("0");}
       if(cmd.equals("CE")){t.setText("");}
       if(cmd.equals("Back")){if(t.getText().equals("") || t.getText().equals("0"))
                                  {t.setText("0");}
                              else
                                  {
                                    String s=t.getText();
                                    t.setText(s.substring(0,s.length()-1));
                                  }}
       if(cmd.indexOf("++")==0 || cmd.indexOf("-")==0 || cmd.indexOf("*")==0 || cmd.indexOf("/")==0)
       {t.setText(t.getText()+cmd);}
       //设置计算结果
       if(result!=null){t.setText(result);}
       }
       };

  public static void main(String[] args) throws Exception{
       CalFrame calculator=new CalFrame ();
       calculator.initialize();
       calculator.setVisible(true);

  }

}

CalService类:

public class CalService {
String lastOp=null;
String firstNum=null;
String secondNum=null;
String opString=”++-*/”;
String numString=”0123456789.”;
String mString=”MCM+MRMS”;
boolean isSecondNum=false;
double store=0;

/**
  *计算四则运算结果
  */
//设置文本框中内容
public String catNum(String cmd,String text){
     String result=cmd;
     //如果目前的text不等于0
     if(!text.equals("0")){
     if(isSecondNum){
        //将isSecondNum标记为false
        isSecondNum=false;
   }
     else{
        //则返回结果为text加上新点击的数字
        result=text+cmd;
   }
}
        //如果有.开头,则在前面补0
        if(result.indexOf(".")==0){
        result="0"+result;
   }
        return result;

}
//设置符号lastOp
public String setOp(String cmd,String text){
//将此操作符设置为上一次的操作
this.lastOp=cmd;
//设置第一个操作数的值
this.firstNum=text;
//将第二个人操作数赋值为空
this.secondNum=null;
//将isSecondNum标记为true
isSecondNum=true;
//返回空值
return null;
}
//四则运算
public String cal(String text,boolean isPercent) throws Exception{
//初始化第二个操作数
this.secondNum=text;
double secondResult=Double.valueOf(secondNum);
//如果除数为0则不处理
if(secondNum.equals(“0”) && this.lastOp.equals(“/”)){return “0”;}
//如果有%操作,则第二个操作数等于两数相乘在除以100
if(isPercent){
secondResult=MyMath.multiply(Double.valueOf(firstNum),MyMath.divide(Double.valueOf(secondNum),100));
}
//四则运算,返回结果赋给第一个操作数
if(this.lastOp.equals(“++”)){
firstNum=String.valueOf(MyMath.add(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“-“)){
firstNum=String.valueOf(MyMath.subtract(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“*”)){
firstNum=String.valueOf(MyMath.multiply(Double.valueOf(firstNum),secondResult));
}
else if(this.lastOp.equals(“/”)){
double num=Double.valueOf(firstNum)/secondResult;
//double num=MyMath.divide(Double.valueOf(firstNum),secondResul);
firstNum=String.valueOf(num);
}
//给第二个操作数重新赋值
secondNum=null;
//把isSecondNum标记为true
this.isSecondNum=true;
return firstNum;
}

/**
  *存储操作数
  */
public String mCmd(String cmd,String text){
     if(cmd.equals("M+")){
       //如果是M+操作,就把计算结果累积到store中
       store=MyMath.add(store,Double.valueOf(text));
 }
     else if(cmd.equals("MC")){
     //如果是MC操作,则清除store
     store=0;
  }
     else if(cmd.equals("MR")){
     //如果是MR操作,则把store的值读出来
     isSecondNum=true;
     return String.valueOf(store);
 }
     else if(cmd.equals("MS")){
     //如果是MS操作,则把计算结果保存到store
     store=Double.valueOf(text).doubleValue();
 }
     return null;

}

/**
  *实现开方,倒数,取反等
  */
public String sqrt(String text){
    //将isSecondNum标记为true
    this.isSecondNum=true;
    //计算结果并返回
    return String.valueOf(Math.sqrt(Double.valueOf(text)));
}
public String setReciprocal(String text){
    //如果text为0,则不求倒数
    if(text.equals("0")){
       return text;
   }
   else{
      //将isSecondNum标记为true
      this.isSecondNum=true;
      //计算结果并返回
      return String.valueOf(1/Double.valueOf(text));
   }
 }
 public String setNegative(String text){
      double text1=Double.valueOf(text);
      text1=-text1;
      return String.valueOf(text1);
 }


/**
  *实现倒退操作
  */
public String backSpace(String text){
     return text.equals("0") || text.equals("")?"0":text.substring(0,text.length()-1);
}
/**
  *清除计算结果
  */
public String clearAll(){
     //将第一,第二操作数恢复为默认值
     this.firstNum="0";
     this.secondNum=null;
     return this.firstNum;
}
public String clear(String text){
     text=null;
     return text;
} 


/**
  *实现中转方法(callMethod)
  */
public String callMethod(String cmd,String text) throws Exception{
     if(mString.indexOf(cmd)!=-1){
     return mCmd(cmd,text);
}
     else if(cmd.equals("C")){
     return clearAll();
}
     else if(cmd.equals("CE")){
     return clear(text);
}
     else if(cmd.equals("BACK")){
     return backSpace("text");
}
     else if(numString.indexOf(cmd)!=-1){
     return catNum(cmd,text);
}
     else if(opString.indexOf(cmd)!=-1){
     return setOp(cmd,text);
}
     else if(cmd.equals("=")){
     return cal(text,false);
}
     else if(cmd.equals("+/-")){
     return setNegative(text);
}
     else if(cmd.equals("1/x")){
     return setReciprocal(text);
}
     else if(cmd.equals("sqrt")){
     return sqrt(text);
}
     else if(cmd.equals("%")){
     return cal(text,true);
}
     else{
     return mCmd(cmd,text);
}

}

}

MyMath类:

import java.math.*;
public class MyMath {
/**
*为一个double类型的数据创建BigDecimal对象
*@param number
*@return
*/
private static BigDecimal getBigDeciaml(double number){
return new BigDecimal(number);
}
//加法
public static double add(double num1,double num2){
BigDecimal first=getBigDeciaml(num1);
BigDecimal second=getBigDeciaml(num2);
return first.add(second).doubleValue();
}
//减法
public static double subtract(double num1,double num2){
BigDecimal first=getBigDeciaml(num1);
BigDecimal second=getBigDeciaml(num2);
return first.subtract(second).doubleValue();
}
//乘法
public static double multiply(double num1,double num2){
BigDecimal first=getBigDeciaml(num1);
BigDecimal second=getBigDeciaml(num2);
return first.multiply(second).doubleValue();
}
//除法
public static double divide(double num1,double num2){
BigDecimal first=getBigDeciaml(num1);
BigDecimal second=getBigDeciaml(num2);
return first.divide(second).doubleValue();
}
}

“`
将上述三个类放在同一个包calculator下即是完成仿Windows计算器的完整代码。

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值