剖析Java中的事件处理与异常处理机制
一、事件处理
其实,由事件处理这个名字自然就想到MFC中的消息响应机制,就我的体会,它们应该算是南桔北枳的情形吧,我怀疑Java中的事件处理这个"新瓶"应是装的MFC中的消息响应这个"旧酒"。
所谓的"事件"即如键盘按键、鼠标点击等这类由动作或什么导致某个状态改变并需要对这个改变作相应响应的这类改变。我们可以将Java中的事件分为按钮、鼠标、键盘、窗口、其它事件这几大类。
事件处理模型
1. 基于继承的事件处理模型(JDK1.0)
JDK1.0中,事件处理是基于继承的,事件先发送到组件,然后沿容器层次向上传播。没有被组件处理的事件会自动地继续传播到组件的容器。——这个与MFC中的原始事件响应顺序或者多态的响应机制似乎一致,而后面说的基于代理的事件处理机制似乎又与MFC的回调机制一致。
具体的处理方法
调用action()方法或handleEvent()方法来获取程序运行时发生的事件,所有组件发生的事件都在此方法中处理。
2、基于代理的事件处理模型(JDK1。1)
在这个模型中,事件被直接送往产生这个事件的组件,
对于每一个组件注册一个或多个称为监听者的类,这些类包含事件处理器,用来接收和处理这个事件。
监听者就是实现了Listener接口的类。事件是只向注册的监听者报告的对象。每个事件都有一个对应的监听者接口。
在Button对象上用鼠标进行点击时,将发送一个ActionEvent事件。这个ActionEvent事件会被使用addActionListener()方法进行注册的所有ActionListener的actionPerformed()方法接收。
基于代理的事件处理模型的特点
①事件不会被意外地处理。在层次模型中,一个事件可能传播到容器,并在非预期的层次被处理。
②有可能创建并使用适配器(adapter)类对事件动作进行分类。
③有利于把工作分布到各个类中。
重点学习这种事件处理模型
3、事件
事件处理的三要素。
(1)事件源 事件源是一个事件的产生者,如按钮、窗口及文本域等。
(2)事件类型 Java中所有的事件都封装成一个类,这些事件类被集中在java.awt.event包,所有的事件类均继承了AWTEvent类和一个方法——getSouce()方法,该方法返回发生事件的对象。
(3)事件监听器 不同的类型事件发生后,由事件监听器接收事件并调用相应的事件处理方法。所有的事件监听器实际上都是一个java.awt.event包中的接口,引入了java.util.EventListener接口。不同事件类型的监听器具有不同的方法。
事件处理步骤
① 程序加入java.awt.event包:
Import java.awt.event;
② 给所需的事件源对象注册事件监听器:
事件源对象.addXXXListener(XXXListener);
③ 实现相应的方法。如果某个监听器接口包含多个方法,则需要实现所有的方法
例:b2.addActionListener(this)
4、事件Adapters(适配器)
实现每个Listener接口的所有方法的工作量是非常大的,为了方便起见,Java语言提供了Adapters类,用来实现含有多个方法的类。
你定义的Listener可以继承Adapter类,而且只需重写你所需要的方法。
例如,窗口事件对应的监听器为WindowListener,它必须实现多个方法,包括windowOpened()、windowClosed()、windowClosing()、WindowIconfied()、WindowDeiconfied()、WindowActivated()、WindowDeactivated(),这样加大了不必要的编程工作量。
如果继承了WindowAdapter,就只需实现其中某一个或几个方法,不需要实现所有的方法。后面很多的例子都只实现windowClosing()一个方法,目的是在关闭窗口时退出系统。
4.1 按钮事件的处理
点击按钮所发生的事件为动作事件
动作事件对应的事件类是ActionEvent类
动作事件对应的事件监听器为: ActionListener
监听器的主要方法:
actionPerformed(ActionEvent e)发生动作事件时被调用
实现动作事件的操作工程:
第一步,注册动作事件监听器addActionListener(ActionListener)。
第二步,实现ActionListener接口的方法:actionPerformed(ActionEvent e)
4.2鼠标事件的处理
触发鼠标事件的事件源通常是一个容器,当鼠标进入、离开容器,或者在容器中单击鼠标、拖动鼠标等操作时,都发生鼠标事件
鼠标事件对应的事件类是MouseEvent类
MouseEvent类中方法:
getX()获取鼠标的X坐标
getY()获取鼠标的Y坐标
getPoint()获取鼠标的位置
鼠标事件对应的事件监听器有两个:MouseListener(或者MouseAdapter)对应鼠标事件,MouseMotionListener(或者MouseMotionAdapter)对应鼠标移动事件。
MouseListener(或者MouseAdapter)的主要方法
MousePressed(MouseEvent e)鼠标按下时的处理方法
MouseReleased(MouseEvent e)鼠标释放时的处理方法
MouseEntered(MouseEvent e)鼠标进入时的处理方法
MouseExited(MouseEvent e)鼠标离开时的处理方法
MouseClicked(MouseEvent e)鼠标点击时的处理方法
MouseMotionListener(或者MouseMotionAdapter)的主要方法
MouseMoved(MouseEvent e)鼠标移动时的处理方法
MouseDraged(MouseEvent e)鼠标拖动时的处理方法
4.3 键盘事件的处理
在具有键盘焦点的组件中按下或释放键盘等操作时,都发生键盘事件
键盘事件对应的事件类是KeyEvent类
KeyEvent类主要方法:
getKeyCode()获得按下或释放的键代码
getKeyText()获得按下或释放的键的字符串
键盘事件对应的事件监听器为:KeyListener或KeyAdapter
主要方法:
KeyPressed(KeyEvent e)按下键盘时的处理方法
KeyReleased(KeyEvent e)释放键盘时的处理方法
4.4 窗口事件的处理
有Window及其扩展类(Frame、Dialog)等才能激发窗口事件,表示窗口处于激活/无效状态、图标/非图标状态或打开/关闭状态等
窗口事件对应的类为WindowEvent,监听器为WindowListener(或WindowAdapter)
主要方法:
windowOpened(WindowEvent e)打开窗口的事件处理
windowClosed(WindowEvent e)关闭窗口的事件处理
windowClosing(WindowEvent e)正在关闭窗口的事件处理
WindowActivated(WindowEvent e)激活状态的事件处理
WindowDeactivated(WindowEvent e)无效状态的事件处理
4.5 其它事件的处理
4.5.1 复选框、单选钮事件处理
事件类是ItemEvent :
选项事件对应的事件监听器为: ItemListener
方法:
itemStateChanged (ItemEvent e)发生选项事件时被调用
4.5.2 滚动条事件处理
调整事件对应的事件类是AdjustmentEvent类 :
调整事件对应的事件监听器为: AdjustmentListener
方法:
adjustmentValueChanged (AdjustmentEvent e)发生调整事件时被调用
4.5.3 下拉列表的事件处理
事件类是ItemEvent :
选项事件对应的事件监听器为: ItemListener
方法:
itemStateChanged (ItemEvent e)生下拉列表发生了动作时被调用
(可见,下拉列表的事件处理与事件类型、事件监听器及方法与复选框、单选框的事件处理的事件类型、事件监听器及方法一样)
4.5.4 菜单事件的处理
菜单事件一般是当我们点击某个菜单项时所发生的事件。
菜单项有两种:
MenuItem 动作事件
CheckboxMenuItem,选项事件
MenuItem的事件处理
第一步,给所有的MenuItem菜单项注册动作事件监听器addActionListener(ActionListener)。
第二步,实现ActionListener接口的方法:actionPerformed(ActionEvent e)。在该方法中用e.getSource()获取用户所选的菜单项,并进行相应的处理。
CheckboxMenuItem的事件处理
第一步,给所有的CheckMenuItem菜单项注册选项事件监听器addItemListener(ItemListener)。
第二步,实现ItemListener接口的方法:itemStateChanged(ItemEvent e)。在该方法中用e.getSource()获取用户所选的菜单项,e.getItem()获取用户所选的菜单项的标签,e.getStateChange()获取是否选中,并进行相应的处理。
二、异常处理
任何好的编程语言和编程人员都不会忽视对异常的处理,作为比较热门的面向对象编程的语言——Java,异常处理机制自然也是其重要特色之一。
一般解释异常,都将其说为:编程中的错误。但是,实际上这个错误可是非常频繁,有多种,如:编译错误、运行错误(具体上又分为:系统运行错误和逻辑运行错误,这个什么系统运行错误,自己倒很少将其算作是编程中的错误了,之前。)
在JAVA中,异常是一个类,它继承自Throwable类。每个异常类代表了运行错误(注意:是运行错误)。异常类中包含了该运行错误的信息及处理错误的方法等内容。
Java的异常处理机制:
每当Java程序运行过程中发生一个可识别的运行错误时,(即该错误有一个异常类与之相对应时),系统都会产生一个相应的该异常类的对象,(注意:叫做产生一个异常类对象。)即产生一个异常。
一旦一个异常对象产生了,系统中就一定要有相应的机制来处理它,确保不会产生死机、死循环或其他对操作系统的损害,从而保证了整个程序运行的安全性
异常和异常类:
Error:由Java虚拟机生成并抛出,Java程序不做处理.
Runtime Exception(被0除等系统错误,数组下标超范围):由系统检测, 用户的Java 程序可不做处理,系统将它们交给缺省的异常处理程序(注意:有缺省的异常处理).
Exception(程序中的问题,可预知的): Java编译器要求Java程序必须捕获或声明所有的非运行时异常
用户自己产生异常
Exception类
构造函数:
public Exception();
public Exception(String s);可以接受字符串参数传入的信息,该信息通常是对该异常所对应的错误的描述。
Exception类从父亲Throwable那里还继承了若干方法,其中常用的有:
1)public String toString();
toString()方法返回描述当前Exception 类信息的字符串。
2)public void printStackTrace();
printStackTrace()方法没有返回值,它的功能是完成一个打印操作,在当前的标准输出(一般就是屏幕)上打印输出当前例外对象的堆栈使用轨迹,也即程序先后调用执行了哪些对象或类的哪些方法,使得运行过程中产生了这个例外对象。
系统定义的运行异常
这些子类有些是系统事先定义好并包含在Java类库中的,称为系统定义的运行异常
用户自定义的异常
对于某个应用所特有的运行错误,则需要编程人员根据程序的特殊逻辑在用户程序里自己创建用户自定义的异常类和异常对象
用户定义的异常通常采用Exception作为异常类的父类
但是这里有一个还未弄懂的问题:发生一个错误,系统怎么知道是可识别的?又是怎么产生相应异常类对象?异常类对象怎么就知道去用相应方法解决?每个处理相应异常的异常类对象难道就只有一个异常处理方法?————————————原来,由用户自定义的异常,是通过语句throw才抛出异常。
创建用户自定义异常时,一般需要完成如下的工作:
1)声明一个新的异常类,使之以Exception类或其他某个已经存在的系统异常类或用户异常为父类。
2)为新的异常类定义属性和方法,或重载父类的属性和方法,使这些属性和方法能够体现该类所对应的错误的信息。
异常的抛出
Java程序在运行时如果引发了一个可以识别的错误,就会产生一个与该错误相对应的异常类的对象,把这个过程叫做异常的抛出,
实际是相应异常类对象的实例的抛出。
根据异常类的不同,抛出异常的方式有系统自动抛出和用户抛出两种:
1、系统自动抛出
所用的系统定义的运行错误异常都是由系统自动地抛出
2、用户抛出
用户自定义的异常不可能依靠系统自动抛出,而必须由用户用Java语句抛出,在Java语句中,throw语句用来明确地抛出一个“异常”
用throw语句抛出的格式
返回类型 方法名(参数列表) throws 要抛出的异常类名列表{
…
throw 异常类实例;//注意这里
…
}
注意:
1)一般当程序中满足某个条件时才抛出异常;
往往把throw语句放在if语句的if分支中,
if(I>100)
throw (new MyException());
2)含有throw的语句的方法,应当在方法头定义中增加如下的部分:
throws 要抛出的异常类名列表
这样做主要是为了通知欲调用这个方法的上层方法,准备接受和处理它在运行中可能会抛出的异常
如果方法中的throw语句不止一个,则应该在方法头throws中列出所有可能的异常
3)Java语言要求所有用throws关键字声明的类和用throw抛出的对象必须是Throwable类或其子类。如果你试图抛出一个不是可抛出(Throwable)对象,Java编译器将会报错
异常处理:
主要考虑如何捕捉异常,捕捉异常后程序如何跳转,以及如何写异常处理语句
1.try…catch…finally 块
1)try
在try语句的{ }中包含了可能会抛出一个或多个异常的一段程序代码
这些代码实际上指定了它后面的catch块所能捕捉的异常的范围。
Java程序运行到try块中的语句时如果产生了异常,就不再继续执行该try块中其他的语句,而是直接进入catch块中寻找第一个与之匹配的异常类型并进行处理。
2)catch块
catch语句的参数类似于方法的定义,包括一个异常类型和一个异常对象。
异常类型必须为Throwable类的子类,它指明了catch语句所处理的异常类型;
异常对象则由Java运行时系统在try所指定的程序代码块中抛出的大括号中包含异常对象的处理的方法代码。
catch语句可以有多个,分别处理不同类型的异常。
Java运行时系统从上到下分别对每个catch语句处理的异常类型进行检测,直到找到与之相匹配的catch语句为止。
这里,类型匹配指catch中的异常类型与生成的异常对象的类型完全一致或者是异常对象的父类,因此,catch语句的排序顺序应该是从特殊到一般。(考虑为什么?)
3)finally块
finally语句可以说是为异常处理事件提供的一个清理机制,一般用来关闭文件或释放其他系统资源
在try-catch-finally语句中可以没有finally部分的语句。
如果没有finally部分,则当try指定的程序代码抛出一个异常时,其他的程序代码就不会被执行;
如果存在finally部分,则不论try块中是否发生了异常,是否执行过catch部分的语句,都要执行finally部分的语句。
可见,finally部分的语句为异常处理提供了一个统一的出口。
多异常处理
一个try块可能会产生多种不同的异常,如果希望能采取不同的方法来处理这些例外,就需要使用多异常处理机制。
多异常处理是通过在一个try块后面定义若干个catch块来实现的,每个catch块用来接收和处理一种特定的异常对象
通过catch块的参数来判断一个异常对象是否应为本catch块接收和处理的异常。
被哪个catch块获取,根据异常对象与catch块的异常参数的匹配情况:当它们满足下面三个条件的任何一个时,认为异常对象和参数匹配:
1)异常对象与参数属于相同的例外类。
2)异常对象属于参数例外类的子类。
3)异常对象实现了参数所定义的接口。
如果try块产生的异常对象被第一个catch块所接收,则程序的流程将直接跳转到这个catch语句块中,语句块执行完后就退出当前方法,try块中尚未执行的语句和其他的catch块将被忽略
如果try块产生的异常对象与第一个catch块不匹配,系统将自动转到第二个catch块进行匹配,如果第二个仍不匹配,就转向第三个、第四个……直到找到一个可以接收该异常对象的catch块,完成流程的跳转。
如果try块产生的异常对象被第一个catch块所接收,则程序的流程将直接跳转到这个catch语句块中,语句块执行完后就退出当前方法,try块中尚未执行的语句和其他的catch块将被忽略
如果try块产生的异常对象与第一个catch块不匹配,系统将自动转到第二个catch块进行匹配,如果第二个仍不匹配,就转向第三个、第四个……直到找到一个可以接收该异常对象的catch块,完成流程的跳转。
若try块中所有语句的执行都没有引发异常,则所有的catch块都会被忽略而不执行。
注意:
1)catch块中的语句应根据异常的不同执行不同的操作
所以在处理多异常时应注意认真设计各catch块的排列顺序。一般地处理较具体和较常见的异常的catch块应放在前面,而可以与多种异常相匹配的catch块应放在较后的位置。
/*尝试用户运行错误的异常处理: 问题是这样的,当输入的用户工资初值少于800则是错误的,当然工资的变化如果超过20%,则也是错误的 */ import java.awt.*; import java.applet.*; import java.awt.event.*; public class UserExceptionApplet extends Applet implements ActionListener{ Label prompt1=new Label("请输入雇员姓名和工资初值:"); Label prompt2=new Label("请输入欲修改的工资"); TextField name,isal,nsal; String msg; Employee Emp; Button okBtn=new Button("OK"); Button cancelBtn=new Button("Cancel"); public void init(){ name=new TextField(5); isal=new TextField(5); nsal=new TextField(5); add(prompt1); add(name); add(isal); add(prompt2); add(nsal); add(okBtn); okBtn.addActionListener(this); cancelBtn.addActionListener(this); add(cancelBtn); } public void paint(Graphics g){ g.drawString(msg,0,80); } public void CreateEmp(String empName,double sa){ try{ Emp=new Employee(empName,sa); msg=new String(Emp.toString()); } catch(IllegalSalaryException ise){ msg=new String(ise.toString()); } } public void ChangeEmpSal(double changeSal){ try{ Emp.setEmpSalary(changeSal); msg=new String(Emp.toString()); } catch(IllegalSalaryException illSal){ msg=new String(illSal.toString()); } catch(IllegalSalaryChangeException illSalChange){ msg=new String(Emp.toString()+illSalChange.toString()); } } public void actionPerformed(ActionEvent e){ String empName; double empSal,changeSal; Object obj=e.getSource(); if(obj==okBtn){ empName=new String(name.getText()); if(empName==null){ msg=new String("请先输入雇员姓名工资并创建之"); } if(nsal.getText()==null){ empSal=Double.valueOf(isal.getText()).doubleValue(); CreateEmp(empName,empSal); } else{ changeSal=Double.valueOf(nsal.getText()).doubleValue(); ChangeEmpSal(changeSal); } } if(obj==cancelBtn){ naem.setText(""); isal.setText(""); nsal.setText(""); } repaint(); } } class Employee{ String m_EmpName; double m_EmpSalary; Employee(String name,double initsalary)throws IllegalSalaryException{ m_EmpName=name;//看这里有问题没,参考代码为m_EmpName=new String(name); if(initsalary<800){ throw(new IllegalSalaryException(this,initsalary));//throw语句 } m_EmpSalary=initsalary; } public String getEmpName(){ return m_EmpName; } public double getEmpSalary(){ return m_EmpSalary; } public boolean setEmpSalary(double newSal) throws IllegalSalaryException,IllegalSalaryChangeException{ if(newSal<800) throw(new IllegalSalaryException(this,newSal)); else if(getEmpSalary()==0.0){ m_EmpSalary=newSal; return true; } else if(Math.abs(newSal-getEmpSalary())/getEmpSalary()>=0.2) throw(new IllegalSalaryChangeException(this,newSal-getEmpSalary())); else{ m_EmpSalary=newSal; return true; } } public String toString(){ String s; s="姓名:"+m_EmpName+"工资: "+m_EmpSalary; return s; } } class IllegalSalaryException extends Exception{ private Employee m_ConcernedEmp; private double m_IllegalSalary; IllegalSalaryException(Employee emp,double isal){ super("工资低于最低工资"); m_ConcernedEmp=emp; m_IllegalSalary=isal; } public String toString(){ String s; s="为雇员提供的工资不合法:雇员:"+m_ConcernedEmp.getEmpName()+"非法工资:"+m_IllegalSalary+"低于最低工资数额800元"; return s; } } class IllegalSalaryChangeException extends Exception{ private Employee m_ConcernedEmp; private double m_IllegalSalaryChange; IllegalSalaryChangeException(Employee emp,double csal){ super("工资变动太大"); m_ConcernedEmp=emp; m_IllegalSalaryChange=csal; } public String toString(){ String s; s="为雇员提供的工资变动不合法:雇员:"+m_ConcernedEmp.getEmpName()+"非法变动工资变化:"+m_IllegalSalaryChange+"高于原工资的20%"; return s; } }