在程序运行过程中出现的错误,称为异常(Exception)。异常就是程序运行过程中出现了不正常现象导致程序的中断。
在Java中,把各种异常现象进行了抽象形成了异常类。
下面,我们来看一下异常的大致继承结构:
一般性异常,又称为受检异常,是指在写代码的过程中会提示的错误,如果这类异常没有解决,那么程序就无法通过编译运行。
RuntimeException(又称运行时异常):是指程序能通过编译,但是运行过程中会出现的问题,意思大致就是程序一运行就会崩溃。
Error(错误):像这类异常,一般指的是JVM的问题哦或者电脑本身硬件的问题,这类异常时硬性问题,可以说与你无关,不是你的错,出现了这类异常你着急也没有用,因为你无法去人为控制它。
RuntimeException(非受检异常)是Java在虚拟机运行期间抛出异常的超类。执行方法期间抛出的RuntimeException的任何子类都无需在throws子句中进行声明,因为它是uncheckedExcepiton。
常见五种RuntimeException:
java.lang.ArithmeticException(算术异常)
java.lang.ClassCastException(类型转换异常)
java.lang.IllegalArgumentException(不合法的参数异常)
java.lang.IndexOutOfBoundsException(数组下标越界异常)
java.lang.NullPointerException(空指针异常)
受检异常定义: Exception 中除 RuntimeException 及其子类之外的异常。特点: Java编译器要求程序必须捕获或声明抛出这种异常
java.io.IOException(IO流异常)
后面四种我也不太熟练,主要用到的是IOException,而且IOException也能满足大部分的异常需求。
java.lang.ClassNotFoundException(没找到指定类异常)
java.lang.NoSuchFieldException(没找到指定字段异常)
java.lang.NoSuchMetodException(没找到指定方法异常)
java.lang.IllegalAccessException(非法访问异常)
java.lang.InterruptedException(中断异常)
注意:执行try、catch或其他地方的return、throw语句前,需要执行finally内的代码。如果在finally中有任何语句,则在执行finally里的语句后,方法才算是结束。
异常的处理,主要分为两种,第一种是try-catch处理,另一种就是throws处理。
try…catch…finally语句块
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
//如果发生异常,则尝试去匹配catch块。}catch(Exception1 e){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。}catch(Exception2 e){
//…
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则,至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
需要注意的点:
try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,
如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去 。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
public static void main(String[] args){ try { foo(); }catch(ArithmeticException ae) { System.out.println("处理异常"); } } public static void foo(){ int a = 5/0; //异常抛出点 System.out.println("为什么要让我掉头发!!!"); //不会执行 }
但是try-catch方法的真正含义是将异常处理掉,如果某一段代码有问题,但是这一段异常被try-catch包住了,那么他后面的程序照样会继续执行下去,相当于这一段有问题的代码转化为了一条提示异常的信息,例如:
public static void main(String[] args) { int a = 1; int b = 0;//从数学角度上看0是不可以做除数的 System.out.println(a/b); System.out.println("哎,我过来了"); }
**
运行结果:
如果没有加try-catch的情况下,他是不能通过编译的,但是加了try-catch后结果如下:
**
public static void main(String[] args) { try { int a = 1; int b = 0;//从数学角度上看0是不可以做除数的 System.out.println(a/b); } catch (Exception e) { e.printStackTrace(); } System.out.println("哎,我过来了"); }
运行结果:*
这里提一下
catch(Exception e) { e.printStackTrace(); }
当try语句中出现异常是时,会执行catch中的语句,java运行时系统会自动将catch括号中的Exception e
初始化,也就是实例化Exception类型的对象。e是此对象引用名称。然后e(引用)会自动调用Exception类中指定的方法,也就出现了e.printStackTrace();
printStackTrace()方法的意思是:在命令行打印异常信息在程序中出错的位置及原因。
在这里,关于异常的提示方式(e.printStackTrace()),我个人认为 用e.toString()会比较好。(不喜勿喷)
因为e.toString获取的信息包括异常类型和异常详细消息。
throws声明:如果一个方法内部的代码会抛出检查异常(checked
exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
总而言之就是,向上级甩锅,谁比较大甩给谁,谁比较牛逼甩给谁**(比如说某天某个小地方,爆发了一种很强的病毒,额,就叫做H9N9吧,地方小县医院处理不了,就得向上级求助,说这个太猛了,我解决不了,把患者甩给市级医院,如果市级医院再处理不了,就再向上抛给省级医院,层层向上,最后在哪一级不再向上抛,要处理时就加try-catch,就给他解决了。)就是异常相当于是患者,throws只能将异常抛出,不能实际地去处理异常,当异常一直向上抛出之后,最后在哪个方法或者哪片区域不再继续抛出了,那么就对这个方法或者这片区域进行try-catch去解决这个异常。
一次性可以抛出单个异常,也可以抛出多个异常
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN { //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。 }
finally块
finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。
良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。
需要注意的地方:
- finally块没有处理异常的能力。处理异常的只能是catch块。
- 在同一try…catch…finally块中,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
- 在同一try…catch…finally块中,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。
这是正常的情况,但是也有特例。关于finally有很多恶心,偏、怪、难的问题,我在本文最后统一介绍了,电梯速达->:finally块和return
throw 异常抛出语句
throw exceptionObject//这里写的是抛出异常的类别
程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public void save(User user) { if(user == null) throw new IllegalArgumentException("User对象为空"); //...... }