12.异常
1.1Java异常体系图
- Throwable (实现类描述Java的错误和异常)
- Error (错误)Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
- Exceprion (异常)如果程序出现了异常,那么一般就需要通过代码去处理了。
- RuntimeException 在编译器是不检查的,出现问题后,需要我们回来修改代码。
- 非RuntimeException 编译器就必须处理的,否则程序不能通过编译,就更不能正常运行了。
疑问:程序出现了不正常的情况,怎么能区分到底是错误还是异常呢?
- 如果不正常情况的消息是以Error结尾的,那么则代表了这是一个错误。
- 如果不正常情况的消息是以Exception结尾的,那么则代表是一个异常。
1.2Throwable类
- toString() 输出该异常的类名。(返回的是用于描述该异常情况的类的完整类名。包名+类名=完整类名)
- getMessage() 输出异常的信息,需要通过构造方法传入异常信息(例如病态信息)。
- printStackTrace() 打印栈信息。
public class Demo30 { public static void main(String[] args) { Throwable throwable = new Throwable("想吐。。。"); System.out.println(throwable.toString());//输出该异常的类名 java.lang.Throwable: 想吐。。。 System.out.println(throwable.getMessage());//输出异常的信息 想吐。。。 throwable.printStackTrace();//打印栈信息 java.lang.Throwable: 想吐。。。at com.boxiaoyuan.test.Demo30.main(Demo30.java:6) } }
1.3程序中的异常处理
1.3.1捕获异常
- try{//可能发生异常的代码 }catch(异常类 变量名){//处理}。
- 如果没有进行try catch处理,出现异常程序就停止。进行处理后,程序会继续执行。
捕获处理要注意的细节:
- 如果一个try块的代码出现了异常,经过处理之后,那么try-catch块外面的代码可以正常执行。
- 如果一个try块出现了异常的代码,那么在try块中出现异常代码后面的所有代码都无法正常执行。
- 一个try块后面可以跟多个catch块,也就是说一个块可以捕获多种异常的类型。
- 一个try块后面可以跟多个catch块,但是捕获的异常类型必须按照从小到大进行捕获。
public class Demo31 { public static void main(String[] args) { div(2, 0); System.out.println("over"); } public static void div(int x, int y) { try { System.out.println(x / y); //可能出现一场的语句,放入try中 }catch(ArithmeticException e) { //进行一场匹配 System.out.println(e.toString()); System.out.println(e.getMessage()); e.printStackTrace(); System.out.println("除数不能为0"); } System.out.println("除法运算"); } }
多个异常
public class Demo32 { public static void main(String[] args) { print(2, 0, null); System.out.println("over"); } public static void print(int x, int y, int[] arr) { try { System.out.println(arr[1]); System.out.println(x/y); }catch(ArithmeticException e) { System.out.println(e.getMessage()); System.out.println(e.toString()); e.printStackTrace(); System.out.println("算术异常"); }catch(ArrayIndexOutOfBoundsException e) { System.out.println(e.toString()); System.out.println(e.getMessage()); e.printStackTrace(); System.out.println("数组角标越界"); }catch(NullPointerException e) { System.out.println(e.toString()); System.out.println(e.getMessage()); e.printStackTrace(); System.out.println("空指针异常"); } System.out.println("函数执行完毕"); } }
多个catch语句之间的执行顺序:
- 是进行顺序执行,从上到下。
- 如果多个catch 内的异常有子父类关系。
- 子类异常在上,父类在最下。编译通过运行没有问题。
- 父类异常在上,子类在下,编译不通过。(因为父类可以将子类的异常处理,子类的catch处理不到)。
- 多个异常要按照子类和父类顺序进行catch。
1.3.2抛出异常
抛出处理要注意的细节:
- 如果一个方法的内部抛出了一个编译时异常对象,那么必须要在方法声明抛出。
- 如果调用了一个声明抛出编译时异常类型的方法,那么调用者必须要进行处理,否则编译报错。
- 一个方法如果遇到了throw关键字,那么该方法就停止执行。
- 在一种情况下只能抛出一种异常对象。
throw与throws的区别:
- throw关键字是用于在一个方法的内部抛出异常对象的,throws是用于在方法上声明抛出异常类型的。
- throw关键字后面跟的是一个异常的对象,throws后面跟的是异常的类型。
- throw关键字一次只能抛出一个异常对象,throws一次可以声明抛出多种异常类型。
什么时候使用抛出处理?什么时候使用捕获处理?
- 如果需要通知调用者出了异常,那么则需要使用抛出处理。如果与用户直接打交道的代码就使用捕获处理,千万不能抛出,一旦抛出就抛给用户。
定义一个功能,进行除法运算。例如:div(int x,int y),如果除数为0,进行处理。功能内部不想处理,或者处理不了。就抛出使用throw new Exception("除数不能为0"); 进行抛出。抛出后需要在函数上进行声明,告知调用函数者,我有异常,你需要处理。如果函数上不进行throws 声明,编译会报错。例如:未报告的异常 java.lang.Exception;必须对其进行捕捉或声明以便抛出throw new Exception("除数不能为0")。
public static void div(int x, int y) throws Exception {//声明异常,告知方法调用者 if(y==0) { throw new Exception("除数不能为0");//throw关键字后面接收的是具体的异常对象 } System.out.println(x/y); }
如何处理声明了异常的函数:
- try{}catch(){}
public static void main(String[] args) { try { div(3, 0); } catch (Exception e) { e.printStackTrace(); } }
- 继续抛出
public static void main(String[] args) throws Exception { div(3, 0); }
总结
- try语句不能单独存在,可以和catch、finally组成 try...catch...finally、try...catch、try...finally三种结构。
- catch语句可以有一个或多个,finally语句最多一个,try、catch、finally这三个关键字均不能单独使用。
- try、catch、finally三个代码块中变量的作用域分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
- 多个catch块的时候,Java虚拟机会匹配其中一个异常类或其子类,就执行这个catch块,而不会再执行别的catch块。(子类在上,父类在下)。
- throw语句后不允许有紧跟其他语句,因为这些没有机会执行。
- 如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。
1.4运行时异常和非运行时异常
1.4.1RuntimeException
运行时异常(RunTimeException以及RunTimeException子类):如果一个方法内部抛出了一个运行时异常对象,那么方法声明可以声明抛出也可以不声明抛出,如果调用了一个声明抛出运行时异常类型的方法,那么调用者可以处理也可以不处理。
RunntimeException的子类:
- ClassCastException:多态中,可以使用Instanceof 判断,进行规避
- ArithmeticException:进行if判断,如果除数为0,进行return
- NullPointerException:进行if判断,是否为null
- ArrayIndexOutOfBoundsException:使用数组length属性,避免越界
1.4.2非运行时异常(受检异常)
如果出现了非运行时异常必须进行throw或者try{}catch(){}处理,否则编译器报错。
常见的非运行异常有io异常和sql异常。
- IOException
- FileNotFoundExcetion
- SQLException
1.4.3函数的重写和异常
运行时异常
public class Demo34 { public static void main(String[] args) { Father father = new Son(); father.test(); } } class Father{ void test() throws ClassCastException{ System.out.println("父类"); throw new ClassCastException(); } } class Son extends Father{ void test() { System.out.println("子类"); } }
函数发生了重写,因为是运行时异常,在父类的test方法中,可以声明throws 也可以不声明throws。
非运行时异常
public class Demo34 { public static void main(String[] args) throws ClassNotFoundException { Father father = new Son(); father.test(); } } class Father{ void test() throws ClassNotFoundException{ System.out.println("父类"); throw new ClassNotFoundException(); } } class Son extends Father{ void test() { System.out.println("子类"); } }
声明非运行时异常的方法,在调用时需要处理,所以在main方法调用时throws,多态时,如果子类方法没有抛出非运行时异常,父类方法抛出非运行时异常,需要处理。
总结
- 1:子类覆盖父类方法时,父类方法抛出异常,子类的覆盖方法可以不抛出异常,或者抛出父类方法的异常或该父类方法异常的子类。
- 2:父类方法抛出了多个异常,子类覆盖方法时,只能抛出父类异常的子集。
- 3:父类没有抛出异常子类不可抛出异常。
- 1:子类发生非运行时异常,需要进行try{}catch的(){}处理,不能抛出。
- 4:子类不能比父类抛出更多的异常。
1.5finally
finally块是程序在正常情况下或异常情况下都会运行的,比较适合用于既要处理异常又有资源释放的代码。在处理异常的时候该语句块只能有一个。
finally是否用于都执行?
- 有一种情况,如果JVM退出了System.exit(0),finally就不执行。
- return都不能停止finally的执行过程。
public class Demo35 { public static void main(String[] args) { FileInputStream fileInputStream = null; try { System.out.println("1创建io流可能会出现异常"); fileInputStream = new FileInputStream("c:\\aa.txt");//加载硬盘上的文本文件到内存 } catch (FileNotFoundException e) { System.out.println("2.没有找到aa.txt文件,catch执行了"); e.printStackTrace(); //return; //System.exit(0); }finally { System.out.println("3.finally执行"); if(fileInputStream!=null) {//如果流对象为null,说明流对象不存在,没必要关闭资源 try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); System.out.println("4.close异常"); } } System.out.println("5.finally over"); } } }
1.6自定义异常
如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
- 一个无参构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数。
- 一个带有String参数和Throwable参数,并都传递给父类构造函数
- 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
下面是IOException类的完整源代码,可以借鉴。
public class IOException extends Exception { static final long serialVersionUID = 7818375828146090155L; public IOException() { super(); } public IOException(String message) { super(message); } public IOException(String message, Throwable cause) { super(message, cause); } public IOException(Throwable cause) { super(cause); } }