异常概述
对程序员来说,需要尽可能的预知所有可能发生的错误情况并且做出相应处理,但是很多时候无法穷举出所有的错误,例如一个只能输入int类型的输入框,我们需要对double、String、Char等等类型作出判断。JAV的异常机制可以让程序一次性处理所有的错误;当程序出现意外情况时,系统会自动生成一个Exception对象通知程序,使得业务处理代码和错误处理代码分离开来;
异常处理机制
try{
}
catch (Exception e){
}
异常类的继承体系
上面代码中的Exception是所有异常类的父类,每个catch块都是专门用于处理该异常类及其子类的异常实例;
当JAVA运行时环境接收到异常对象后,会依次判断该异常对象是否是catch中定义的异常类或其子类实例,如果是,就调用该catch块,否则接着往下比较;一般只会有一个catch块被调用;
JAVA把所有的非正常情况分为两种:异常和错误,他们都继承Throwable父类。Error错误一般是与虚拟机相关的错误,这种错误无法恢复或无法捕捉,通常应用程序无法处理这些错误;
多异常捕获
try{
}
catch (IndexOutOfBoundsException|ArithmeticException|NumberFormatException e){
}
捕获多种异常时,异常变量(上面的e)有隐式的final修饰,无法对其进行重新赋值;
访问异常信息
Java程序可以通过访问catch块后的异常形参来获得异常对象的相关信息,当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。
所有异常对象都包含了如下几种常用方法:
getMessage():返回该异常的详细描述字符串。
printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
getStackTrace:返回该异常的跟踪栈信息。
使用finally回收资源
try中的代码有时候会打开一些需要显式回收的物理资源,这时候回收代码如果写在try里,当try中抛出异常时回收代码没法执行,写在catch里正常运行时又没法回收,因此JAVA提供了finally块,finally块总会被执行;
JAVA9的自动关闭资源的try语句
JAVA9允许try后面紧跟一对圆括号,括号中可以声明、初始化资源,这些资源会在try中语句执行完后自动回收;
为了保证try语句能正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口;
Checked异常和Runtime异常体系
Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有RuntimeException类及其子类实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则称为Checked异常。
只有Java语言提供了Checked异常,其他语言都没有提供Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。
对于Checked异常的处理方式有两种:
(1)当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后对应的catch块中修补该异常。
(2)当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try…catch块来捕捉Runtime异常。
当使用throw语句自行抛出异常,如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明的方法中,即把该异常交给该方法的调用者处理;也就是说当出现了(不管是自行抛出的,还是系统抛出的)Checked异常,就要想办法去处理它,不能不理会它,要么显式地在try…catch块里捕获,处理它;要么把它放在一个带throws声明的方法中,把异常交给该方法的调用者处理。
如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中;程序既可以显式使用try…catch来捕获,并处理异常,也可以完全不处理该异常,把异常交给该方法调用者处理。
使用throws声明抛出异常
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道怎么处理,也能使用throws把异常抛给JVM处理,JVM处理方法为:打印异常跟踪栈信息,并且中止程序运行。
抛出异常的几种情况:
1.没有使用try…catch捕获异常,也没有throws声明,当不存在ssss.txt时,程序报错,提示“必须对其捕获或者声明“;
public static void main(String args[]){
FileInputStream fis = new FileInputStream("ssss.txt");
}
2.使用了try…catch捕获异常,当不存在ssss.txt时,程序不会报错,控制台输出“异常”;
public static void main(String args[]){
try{
FileInputStream fis = new FileInputStream("ssss.txt");
}
catch(Exception e){
System.out.println("异常");
}
}
3.使用throws抛出异常,系统会打印异常的跟踪栈信息
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream("ssss.txt");
}
对于带throws声明的方法在进行重写时,子类方法声明的异常必须与父类相同或者是其子类。
抛出异常和处理异常
抛出异常指的是这部分代码自己不处理异常,把异常丢给调用自己的上级,上级如果也不能处理就会接着上抛;处理异常指的是代码自己处理这部分异常;
抛出异常的两种方式
一、显示地通过throws向上级抛出异常
抛出原因:当程序中可能出现异常,但又不想在本级代码中处理时,这时候就会向上一级抛出异常。
抛出作用:向上级抛出异常可以通知上级,我这里可能会出现异常,并且可以告诉上级我这里可能出现哪些异常,需要上级处理或上级再向上上级抛出…,不管上级是否进行异常信息处理,其都要显示的进行(处理或向上上级抛出)。
二、隐式地抛出异常(即在没有语法错误但可能出现逻辑或其他异常的地方)
在程序代码中,当没有语法错误,但可能出现逻辑或其他异常的时候(例如:在数据库引起的异常),正常情况下程序是可以正常运行的,但当出现例外时就会报异常,这种异常很多时候我们并没有人为显示的去对异常进行处理,而是代码中隐地逐层向上级抛出,最终在JDK底层进行异常处理。
PS:对于隐式抛出的异常,在需要处理异常的中间环节方法中可以直接try、catch捕捉异常并进行处理。
使用throw抛出异常
有时候,对于某种情况是否是异常,需要由应用的业务需求决定,在某些情况下是异常,有些情况下不是异常;例如下五子棋,当用户输入的下棋位置已经有棋子了,这时该引发异常;当该位置没有棋子的时候,应该不引发异常。
用户可以通过throw自己定义异常,当new出异常实例并且抛出后,系统中断当前的执行流,并且开始执行catch里面的语句;如以下代码会输出“异常”:
public static void main(String args[]){
try{
int a =0;
throw new Exception("异常");
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
之前处理异常的方式有两种:
1.在出现异常的方法内捕获并且处理异常,方法的调用者不能捕获到这个异常;
2.抛出异常给方法的调用者去处理;
我们也可以在catch中定义throw,使得当前的方法能够处理这个异常,方法的调用者也能捕获到这个异常,两者共同处理异常;
异常链模式
对于底层代码的异常信息,把其直接向上传递暴露给上层的用户层是不负责任的行为,一般的做法是:程序捕获原始异常并且保存,然后抛出一个新的业务异常,新的异常中包含了对用户的提示信息,这也被称为“异常链模式”。
所有的Throwable子类在构造器里都可以接收一个cause对象作为参数,这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并且抛出了新的异常,也能通过这个异常链追踪到异常最初的位置。
public static void main(String args[]) throws SQLException {
try{
int a =0;
}
catch(Exception e){
throw new SQLException(e);
}
}
T