【捕获异常】
硬件的错误、输入错误、物理限制等问题,都可能导致程序运行时的异常出现。
1.异常的分类层次
在java中,异常对象都是由Throwable类继承而来的,主要分为两大类:
Error和Exception类。
在Exception中又分为:
RuntimeException异常和非RuntimeException异常。
异常的分类有以下几种:
1.Error类层次的结构主要描述了java运行时系统的内部错误和资源耗尽等错误,如硬件错误、输入错误数据、系统崩溃等。出现这些系统内部的问题,除了通知用户并尽量安全退出,基本上程序员也无能为力了,所以这种情况一般不要求解决,当然,这种情况也很少出现。
2.RuntimeException的异常时指运行时异常,也称为未检查异常。一般是由于程序员自己的问题造成的,如错误的类型转换、数组访问越界、访问空指针等。这些都是可以通过程序员的细心处理和认真分析来避免的。如在转换类型时检查,通过检查下标来防止越界,使用变量前检查是否为空来杜绝空指针异常。
3.非RuntimeException异常,也就是已检查异常。常见的已检查异常主要有以下几种:java.io.IOEeception(IO异常,输入输出设备不存在,比如在文件的末尾读取数据)和java.io.FileNotFoundException(文件未找到异常)和java.lang.ClassNotFoundException(动态地装载某个类时,找不到此类)
注意:对于已检查异常必须处理,对于未检查异常可以处理也可以不处理
Java异常类层次结构图:
2.捕获和处理异常
语法规则:
- 返回类型 方法名(参数)
- {
- try {
- 可能出现的异常语句A
- 可能出现的异常语句B
- 可能出现的异常语句C
- }
- catch(异常类 e) {
- //某种异常的引用
- 异常处理的语句D
- }
- finally {
- 最终一定会执行的语句E
- }
- 其他语句F
- }
finally语句块内的语句无论在什么时候在什么情况下都会被执行,经常会写一些释放资源的语句。如果前面语句中有一个return的值,finally中也有一个return的值,那么返回的一定是fianlly中return的值。
finally和catch可以省略其中的一个,但是不能同时省略
示例:
- public class ExceptionTest {
- public static void main(String[] args) {
- //写一个for循环和switch分支语句,一次运行会抛出异常的语句
- for(int i = 0;i < 5;i++) {
- int j = 0;
- try {
- System. out.println("本条语句正常" );
- //每次运行1条语句,前4条抛出异常,最后一条不抛出异常
- switch(i) {
- case 0: int zero = 0;
- j = 1/zero; break;
- case 1: int b[] = null;
- j = b[0]; break;
- case 2: int c[] = new int [2];
- j = c[3]; break;
- case 3: char ch = "hello".charAt(9); break;
- case 4: char cha = "abc".charAt(1); break;
- }
- System. out.println("前面的语句都没有异常抛出才会运行我" );
- } catch(Exception e) {
- System. out.println("第" + (i+1) + "次捕获异常");
- System. out.println("显示的异常信息为:" + e);
- } finally {
- System. out.println("无论前面怎样,这条语句都会执行" );
- }
- }
- }
- }
上面代码中case的前四句话必定会抛出异常:
case:0是零做了除数;
case:1是数组没有赋值,也就是没有初始化就使用
case:2是数组越界
case: 3是字符串越界
结果:
3.异常的匹配
如果try语句后跟多个catch语句,那么排列的先后顺序如下所示:
1.如果多个catch语句块中所指定的异常类型级别相同或者没有任何派生关系,则catch语句排列无序。
2.如果多个catch语句块中所指定的异常类型相互之间有派生关系,则必须子类型的异常写在上面,父类型的异常写在下面。
【自定义和抛出异常】
1.自定义异常
当系统中已有的异常类型不能满足使用要求时,可以根据自己的需要抛出自定义异常的对象。
自定义异常类一般通过充当捕获异常的角色,所以从Exception类或者其他的捕获异常类继承就可以了
其语法格式如下:
- class 类名 extends Exception {
- 类体;
- }
自定义的异常类一般都会编写必要的构造方法和一些适当的功能方法,而且一般都会有两个构造方法:
一个是无参的构造方法
一个是以字符串做参数的构造方法
以字符串做参数的构造方法可以在有出错信息的情况下创建异常对象。
示例:
- //自定义异常类ReportNotFoundException,有两个构造方法
- public class ReportNotFoundException extends Exception{
- ReportNotFoundException() {
- }
- ReportNotFoundException(String mesg) {
- super(mesg);//创建异常对象
- }
- }
2.抛出异常
在声明或者方法中抛出异常,主要内容和使用方法如下:
在编程中会遇到很多异常,如果该程序有能力解决,可以用try/catch方法主动获取和处理异常。当不具备对异常进行处理的能力时,则向上抛出异常,直到抛到能够处理异常的位置。
抛出异常的语法是:
- 访问限制修饰符 [static] 方法名(参数列表) throws 异常序列 {
- 方法体;
- }
比如,有一个m方法抛出了IOException和InterruptedException。
代码如下:
- public void m() throws IOException,InterruptedException {
- 方法体;
- }
throws用来声明该方法将有可能抛出的异常,将可能抛出的异常列在throws后面,用逗号隔开
在方法体中处理异常的时候,也可以将这两种捕获异常再抛出,使用throws语句就可以将一个异常对象抛出
语法格式为:
- throw new 异常对象;
当一个方法中出现异常,而没有处理,则以异常对象为返回值返回调用处,然后逐级传递。如果一直没有捕获和处理,最终异常将返回虚拟机,虚拟机终止退出,程序结束。
示例:
自定义异常:
- //自定义异常类ReportNotFoundException,有两个构造方法
- public class ReportNotFoundException extends Exception{
- ReportNotFoundException() {
- }
- ReportNotFoundException(String mesg) {
- super(mesg);//创建异常对象
- }
- }
职员及相应方法:
- public class Employee {//定义公开的职员类型
- String name;
- public Employee(String name) {
- this.name = name;
- }
- public String getReport() throws ReportNotFoundException {
- //定义职员找报表的方法,找不到报表就报自定义异常
- if(Math.random() > 0.7) {//设定找到报表的几率是70%
- throw new ReportNotFoundException(name + "找不到报表" );
- }
- return name + ",报表找到了" ;
- }
- }
- class Manager {//定义经理类
- Employee[] es;//职员数组
- public Manager(Employee[] es) {
- super();
- this.es = es;
- }
- public String getReport() throws ReportNotFoundException {
- //定义经理找报表的方法
- StringBuffer sb = new StringBuffer();//StringBuffer定义经常变化的字符串
- for(int i = 0;i < es.length;i++) {
- sb.append( es[i].getReport());//经理让所有职员找报表
- }
- return sb.toString();
- }
- }
- class CFO {//定义CFO类
- Manager[] ms;
- public CFO(Manager[] ms) {
- super();
- this.ms = ms;
- }
- public String getReport() {//CFO的找报表方法,CFO只找下面的经理要报表
- try {
- return ms [0].getReport();
- } catch(ReportNotFoundException e) {
- e.printStackTrace();//打印异常栈信息
- }
- return null ;
- }
- }
- class CEO {//定义CEO类
- CFO cfo;
- public CEO(CFO cfo) {
- super();
- this.cfo = cfo;
- }
- public String getReport() {//CEO的找报表方法,调用CFO去找报表
- return cfo.getReport();
- }
- }
测试:
- //测试向上抛出方法和自定义异常
- public class ExceptionTest2 {
- public static void main(String[] args) {
- Employee emone = new Employee("员工一");//创建员工对象
- Employee emtwo = new Employee("员工二");
- Employee emthree = new Employee("员工三");
- Employee[] employees = new Employee[] {emone,emtwo,emthree};//创建员工数组
- Manager[] managers = new Manager[] {new Manager(employees)};//创建经理数组
- CFO cfo = new CFO(managers );//创建CFO对象
- CEO ceo = new CEO(cfo);//创建CEO对象
- System. out.println(ceo.getReport());//ceo对象调用找报表方法
- }
- }
结果:
小结:
- try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
- catch 块:用于处理try捕获到的异常。
- finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。
- try-catch-finally 规则(异常处理语句的语法规则):
1) 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。
2) 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。
3) catch 块与相应的异常类的类型相关。
4) 一个 try 块可能有多个 catch 块。若如此,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块
5) 可嵌套 try-catch-finally 结构。
6) 在 try-catch-finally 结构中,可重新抛出异常。
- try、catch、finally语句块的执行顺序:
1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
- Throws抛出异常的规则:
1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
2)必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
3)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
4)调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
- 常见异常
在Java中提供了一些异常用来描述经常发生的错误,对于这些异常,有的需要程序员进行捕获处理或声明抛出,有的是由Java虚拟机自动进行捕获处理。Java中常见的异常类:
1.runtimeException子类:
1、 java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
2、java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
3、java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
4、java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
5、java.lang.NegativeArraySizeException
数组长度为负异常
6、java.lang.ArrayStoreException
数组中包含不兼容的值抛出的异常
7、java.lang.SecurityException
安全性异常
8、java.lang.IllegalArgumentException
非法参数异常
2.IOException
1、IOException:
操作输入流和输出流时可能出现的异常。
2、EOFException
文件已结束异常
3、FileNotFoundException
文件未找到异常
3. 其他
1、ClassCastException
类型转换异常类
2、ArrayStoreException
数组中包含不兼容的值抛出的异常
3、SQLException
操作数据库异常类
4、NoSuchFieldException
字段未找到异常
5、NoSuchMethodException
方法未找到抛出的异常
6、NumberFormatException
字符串转换为数字抛出的异常
7、StringIndexOutOfBoundsException
字符串索引超出范围抛出的异常
8、IllegalAccessException
不允许访问某类异常
9、InstantiationException
当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常