一、对异常的本质理解
java异常也属于一个jav对象(继承Object),它封装了一些成员变量和操作。异常就是一种错误情况,是程序不希望出现的现象,但是由于程序本身的设计思路和运行环境等因素,出现异常的情况是无法避免的,java虚拟机针对不同的异常情况定义了很多的异常类,当JVM运行程序发现对应的异常时,就会向外界抛出异常。
二、异常的分类
所有的java异常类均继承于Throwable类,而Throwable类有两个子类分别是Error和Exception类。
Error类:由于虚拟机自身的原因所导致出现的错误,比如:虚拟机内部错误或者内存溢出,当该异常出现时,除了重启之外基本毫无能力。
Exception类:由于程序本身的运行情况所导致的错误,包括程序逻辑问题以及程序IO交互问题。
Exception类包含两类错误:
(1)RuntimeException(运行时异常):这个是属于程序设计的逻辑存在问题,当该异常出现的时候,尽量不要使用捕获异常来解决该,应该从程序设计的角度来避免该异常的出现。
- NullPointerException:空指针异常,这是由于定义了一个变量在没有实例化之前就调用引起的异常
- ClassCastException:类型强制转换异常
- IllegalArgumentException:不合法参数异常
- ArithmeticException:算数运行异常
- ArrayStoreException:数组中添加非法对象类型异常
- ArrayIndexOutOfBoundsException:数组下标越界异常(这个异常比较常见)
- NegativeArraySizeException:创建数组使用负数来确定数组大小时将会报这个错误
- NumberFormatException:数字格式化异常,这个通常在将字符串的数字转换为int类型时报的错误,可能是字符串根本不能转换(不是数字或者数字拼接了空格之类)
(2)checkedExcption(检查异常):
可以在编写程序时静态代码块需要解决的异常,如IOException,SQLException。
异常有关的关键字:try,catch,finally,throw,throws。
IOException(IO异常):这一类异常通常是程序与资源打交道或者以接口打交道造成,程序本身并不能控制资源和接口的正确性,因此这一类异常在程序中是允许发生的,通常我们需要捕获这一类异常来确定在发生异常的情况下应该做什么.
- ClassNotFoundExcpetion:找不到类异常,使用反射时经常出现
- NoClassDefException:类依赖异常
- FileNotFoundException:找不到文件异常
总结: java对异常的处理说白了及时能通过程序控制的就尽量不让它出现,不能通过程序控制的就捕获它来处理,此外我们应该记住一些基本的异常。
三、初始异常
代码演示:
这个错误的产生是由于整数不能为0.
异常是指在执行某个函数的时候所引起的,函数是层级调用,形成调用栈的,只要一个函数发生了异常,那么那么它的所有的都会被异常所影响,当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。
第一行:异常追踪的路径,从调用栈的栈顶往底回溯;异常的线程;异常类型;异常原因的说明文字
第二行括号:异常抛出点。
at 类名.方法名(文件名:异常代码行号)
从上面的例子可以看出,当divide函数发生除0异常的时候,devide函数将抛出ArithmeticException异常,因此调用他的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
上面的例子不使用异常处理机制,也可以完成编译,因为是非检查异常。如果异常是检查异常,必须使用异常处理机制。
四、异常处理的基本语法
在编写代码进行异常处理的时候,对于检查异常,通常有2种处理方式:使用try......catch......finally语句块处理,或者在函数签名处使用throws声明交给它的函数调用者celler去解决。
try......catch......finally语句块:
try{
//try块里面放可能产生的异常
//如果执行完try块且不发生异常的话,紧接着去执行finally和finally块语句后的内容
//如果发生异常,则尝试去匹配catch语句块后的内容
}
catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者异常类型的子类。可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}
catch
(Exception exception){
//...
}
finally
{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则,至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
注意:
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。
3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。-----------终结式异常处理模式
4、throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
代码演示:
public void fun() throws ExceptionType1,ExceptonType2,ExceptionType3{
//fun()内部可以抛出ExceptionType1 ExceptionType2 ExceptionType3类的异常,或者他们子类的异常对象。
}
5、throw异常抛出语句 throw exceptionObject
也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点。
代码演示:
public void fun(use user){
if(user==null)
throw new IllegalArgumentException("User对象为空");
.......
.......
}
6、finally语句块:finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等。
需要注意:finally块没有处理异常的能力。处理异常的只能是catch块;在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块;在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。