前言:JAVA代码日常开发,一个很必要的习惯就是异常处理习惯,JAVA提供了异常处理机制,保证了代码的健壮性和稳定性。
异常处理机制:发生异常时,按照代码预先设定的异常处理逻辑,针对性的处理异常,让程序尽可能恢复正常并继续执行,且需要保持代码的清晰。JAVA中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw语句手动抛出的,只要在JAVA程序中产生了异常,就会用一个对应类型的异常对象来封装异常。
Throwable类是JAVA异常类型的顶层父类,一个对象只有是Throwable类的实例,他才是一个异常对象,才能被异常处理机制识别。JDK中存在一些常用的异常类,我们同样自己可以自定义异常。
JAVA异常分类与类结构图
顶层父类:Throwable
派生类:Error类与Exception类
Error代表的是错误,一般为JVM错误,错误不能被程序员使用代码处理,Error出现较少。
Exception是主要和代码打交道的部分,主要讨论的就是Exception。
异常分类两种:非检查异常与检查异常
非检查异常:Error与RuntimeException以及他们的子类,javac编译的时候不会提示发现这些异常,不强制要求在程序处理这些异常。但是存在这些异常代表代码有问题,需要去改正代码。
检查异常:除非检查异常外的其他异常,javac编译的时候会强制要求程序员处理这些可能产生异常的代码片段,常用的就是try{}catch(){},throws。要么使用try catch捕获异常并处理,要么使用throws子句声明抛出,否则不可以编译过去。
关键点:javac用来区分非检查异常与检查异常。
简单非检查异常实例分析:
package test;
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("计算开始!");
calculate();
}
public static void calculate(){
int n = 5;
int m = 0;
int result = devide(n,m);
System.out.println("result:"+result);
}
public static int devide(int n, int m){
return n/m;
}
}
当devide函数除数为0时发生异常,devide()函数将抛出ArithmeticException异常,调用他的calculate也无法正常完成,因此也发送异常,而caculate的调用者main同样会发生异常,这样一直想调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的调用者中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
使用try...catch,解决一下异常:
package test;
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("计算开始!");
try{
calculate();
}catch(ArithmeticException e){
System.out.println("除数为零");
e.printStackTrace();
}
sum();
}
public static void sum(){
int n = 5;
int m = 4;
int result = n+m;
System.out.println("SumResult:"+result);
}
public static void calculate(){
int n = 5;
int m = 0;
int result = devide(n,m);
System.out.println("result:"+result);
}
public static int devide(int n, int m){
return n/m;
}
}
其中e.printStackTrace();用于打印异常回溯。
简单检查异常实例分析:
编写代码后检查异常直接回显示报错的地方,如下图所示,三处变红的地方,之后通过throws来抛出异常。
package test;
import java.io.FileInputStream;
import java.io.IOException;
public class ExceptionIODemo {
public static void main(String[] args) {
}
public void testException() throws IOException{
String fileStr = "";
FileInputStream file = new FileInputStream(fileStr);
int word;
while((word = file.read())!=-1){
System.out.println((char)word);
}
file.close();
}
}
异常处理的基本语法
在编写代码处理异常时,对于检查异常,有2种不同的处理方式:使用try…catch…finally语句块处理它。或者,在函数签名中使用throws 声明交给函数调用者调用者去解决。
1. try catch finally 语句块
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
//如果发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个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异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
(3)java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
2. throws函数声明
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
3. throw函数声明
程序可以用throw语句抛出明确的异常。Throw语句的通常形式如下:
throw ThrowableInstance;
这里,ThrowableInstance一定是Throwable类类型或Throwable子类类型的一个对象。简单类型,例如int或char,以及非Throwable类,例如String或Object,不能用作异常。有两种可以获得Throwable对象的方法:在catch子句中使用参数或者用new操作符创建。
程序执行在throw语句之后立即停止;后面的任何语句不被执行。最紧紧包围的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
package test;
public class ThrowExceptionDemo {
public static void demoTest() {
try {
throw new NullPointerException("demo");
} catch (NullPointerException e) {
System.out.println("捕获demoTest");
throw e;
}
}
public static void main(String[] args) {
try {
demoTest();
} catch (NullPointerException e) {
System.out.println("重新捕获:" + e);
}
}
}
该程序有两个机会处理相同的错误。首先,main()设立了一个异常关系然后调用demoTest( )。 demoTest( )方法然后设立了另一个异常处理关系并且立即抛出一个新的NullPointerException实例,NullPointerException在下一行被捕获。异常于是被再次抛出。下面是输出结果:
捕获demoTest
重新捕获:java.lang.NullPointerException: demo
该程序还阐述了怎样创建Java的标准异常对象,特别注意下面这一行:
throw new NullPointerException("demo");
这里,new用来构造一个NullPointerException实例。所有的Java内置的运行时异常有两个构造函数:一个没有参数,一个带有一个字符串参数。当用到第二种形式时,参数指定描述异常的字符串。如果对象用作 print( )或println( )的参数时,该字符串被显示。这同样可以通过调用getMessage( )来实现,getMessage( )是由Throwable定义的。