异常概述
异常机制有两个主要作用:一是当程序在运行过程中引发异常时,不会中断程序的执行影响其他业务流程;二是通过手动抛出运行时异常实例的方式对业务处理流程中的异常情况统一处理,在一定程度上简化数据有效性、业务非法性等等的校验操作。
异常处理机制
1、如果程序出现了异常,那么会自动地由JVM根据异常的类型实例化一个指定异常类的对象。
2、如果程序中没有任何异常处理的操作,则这个异常类的实例化对象将交由JVM进行处理,而JVM的默认处理方式就是进行异常信息的打印,而后中断该程序执行。
3、如果程序做了异常处理策略,将会由try语句捕获异常类对象;
4、将捕获的对象与每一个catch进行匹配,如果匹配成功则使用指定的catch进行处理,如果没有任何的catch匹配成功,则将异常交由JVM处理。
在上述的处理机制中,catch块的使用尤为重要。在JDK1.7以前每个catch块只能匹配一种异常类型,而且这种匹配方式与catch块定义时的顺序有关,这就要求我们在定义catch块时需要按照异常类型的继承关系,从继承树底端到顶端顺序进行排列,才能保证异常实例的精确匹配。在JDK1.7及以后,每个catch块允许定义多个异常类型,极大的简化了异常处理的代码。
异常继承关系
常见异常类继承关系图可以看出,Java将程序的异常情况分为两大类,一类是错误级别,一类是异常级别,它们都继承自Throwable接口。其中Error错误,一般是指与虚拟机相关的异常问题,比如系统崩溃、虚拟机错误、动态链接失败等,这种错误一旦出现,将直接中断程序的执行,无法进行捕获处理;而Exception异常,是指程序可以处理的异常,可以通过try..catch程序进行处理,对于异常程序员应做提前预测,对有可能出现的异常进行事先处理,防止由异常导致的程序中断。
package com.icypt.exception;
import java.lang.*;
import java.io.*;
/**
*运行结果
*执行IOException所在catch块
*java.io.FileNotFoundException: D:\temp.txt (系统找不到指定的文件。)
* at java.io.FileInputStream.open0(Native Method)
*我始终会被执行哦!
*/
public class Test1 {
public static void main(String[] args) {
//异常处理机制
InputStream is = null;
//try块
try {
is = new FileInputStream("D:\\temp.txt");
} catch(IOException e1) { //catch块
System.out.println("执行IOException所在catch块");
e1.printStackTrace();
} catch(Exception e2) { //catch块
System.out.println("执行Exception所在catch块");
e2.printStackTrace();
} finally { //finally块
System.out.println("我始终会被执行哦!");
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
由运行结果可以看出虽然程序抛出了异常,但是由于我们事先对异常进行了处理,所以并不妨碍正常业务逻辑的执行,这就是异常处理机制的优势。需要注意的是,在finally块中我再次使用try catch语句来捕获执行close方法有可能引发的异常,这种编码方式称之为异常嵌套,关于异常的嵌套原则上是可以嵌套多层,但是从代码的可读性、简洁性来讲,最多不要超过两层。在程序中还使用到了输入流,关于流的使用大家先做了解,后续文章会详细介绍。
获取异常信息
当catch语句捕获到相应的异常后,会将异常对象赋值给异常类型后的形参,我们可以通过该形参来获取异常的相关信息,下面列举四个常用的异常信息获取方法。
//返回对异常的描述信息
public String getMessage() {}
//通过标准错误输出打印异常信息
public void printStackTrace() {}
//将异常的跟踪栈信息输出到指定的输出流
public void printStackTrace(PrintStream s) {}
//获取异常的跟踪栈信息
public StackTraceElement[] getStackTrace() {}
所谓异常的跟踪栈信息就是指由引发异常的位置起,JVM会沿着异常方法的调用链逆向采集异常的捕获过程,直到异常被捕获或者最终没有被捕获而是交由JVM处理而终止。异常跟踪栈信息方便程序员在遇到异常时可以很快的定位引发异常的位置,是排查异常的最关键信息。所以建议在编写try catch语句时一定要有针对性的处理异常,不要把编写一个庞大的try块而后使用catch(Exception e)来捕获异常。这样做的后果就是一旦程序引发异常,我们将不太容易定位异常的位置,对我们排查异常代码增加了复杂度。
栗子,获取异常信息
package com.icypt.exception;
import java.lang.*;
import java.io.*;
/**
*运行结果
*返回对异常的描述信息:null
*打印异常信息:
*java.lang.NullPointerException
* at com.icypt.exception.Test2.main(Test2.java:12)
*将异常的跟踪栈信息输出到指定的输出流:
*java.lang.NullPointerException
* at com.icypt.exception.Test2.main(Test2.java:12)
*获取异常的跟踪栈信息:
*第1条跟踪栈信息,行号:12,类名称:com.icypt.exception.Test2,方法名称:main
*/
public class Test2 {
public static void main(String[] args) {
try {
String str = null;
str.contains("a");
} catch (Exception e) {
System.out.println("返回对异常的描述信息:" + e.getMessage());
System.out.println("打印异常信息:");
e.printStackTrace();
System.out.println("将异常的跟踪栈信息输出到指定的输出流:");
PrintStream ps = new PrintStream(System.out);
e.printStackTrace(ps);
ps.println();
System.out.println("获取异常的跟踪栈信息:");
StackTraceElement[] stArray = e.getStackTrace();
if(stArray != null) {
for(int i=0; i < stArray.length; i++) {
System.out.println("第" + (i+1) + "条跟踪栈信息,行号:" + stArray[i].getLineNumber()
+ ",类名称:" + stArray[i].getClassName() + ",方法名称:" + stArray[i].getMethodName());
}
}
}
}
}
获取异常信息的意义在于,很多时候捕获到异常之后并不是简单的打印一下异常信息就完事了,这种处理异常的方式太过草率。我们要尽可能的通过异常处理程序弥补异常所带来的缺陷。同时将异常信息进行处理之后以比较温馨的描述方式反馈给需要的调用者,最后再把异常跟踪栈信息以日志文件或者数据库的方式存储起来,以方便事后排查错误。
两大异常体系
Java中的异常又分为checked异常和非checked异常,checked异常是Java要求必须显式处理的异常,如果不做处理则编译无法通过。而处理的方式无非就两种,一是使用try catch语句进行处理;二是将捕获到的异常继续抛出由上级调用处处理,例如IOEException、SQLException等。非checked异常也叫运行时异常,它们都是RuntimeException类或者是其子类,Java对运行时异常并未强制要求显式处理,程序开发人员可根据业务实际情况进行处理即可,例如ClassCastException、NullPointerException等。
手动抛出异常
Java也允许程序开发人员手动抛出异常,手动抛出异常需要使用throw关键字来完成。
手动抛出异常语法
throw new 异常类();
package com.icypt.exception;
import java.lang.*;
import java.io.*;
/**
*运行结果
*Exception in thread "main" java.lang.RuntimeException
* at com.icypt.exception.Test3.throwException(Test3.java:16)
* at com.icypt.exception.Test3.main(Test3.java:11)
*/
public class Test3 {
public static void main(String[] args) {
throwException();
}
//手动抛出异常
public static void throwException() {
throw new RuntimeException();
}
}
可以看出,我们手动抛出了一个运行时异常类实例并且在main方法中并没有对其进行处理,所以异常的处理就交由JVM来进行,而JVM的处理方式就是打印异常栈信息后中断程序的执行。
自定义异常类
在实际的开发中,我们可能需要根据业务模块的不同而定义不同的异常类,以方便程序出现异常时可以很快的进行定位。而自定义异常类的思路就是继承Exception类或者RuntimeException类,同时提供方便创建异常对象的构造方法和获取异常信息的对象方法,下面以继承RuntimeException类为例来自定义一个异常类。
package com.icypt.exception;
import java.lang.Exception;
/**
*运行结果:
*异常码:err-001
*异常信息:自定义异常抛出
*打印异常信息:
*com.icypt.exception.BusinessException: 自定义异常抛出
* at com.icypt.exception.BusinessException.main(BusinessException.java:30)
*/
public class BusinessException extends RuntimeException{
//异常码
private String code;
//无参构造
public BusinessException() {}
//构造方法重载
public BusinessException(String message) {
super(message);
}
//构造方法重载
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
//获取异常码
public String getCode() {
return code;
}
public static void main(String[] ars) {
try {
throw new BusinessException("err-001", "自定义异常抛出");
} catch(BusinessException be) {
System.out.println("异常码:" + be.getCode());
System.out.println("异常信息:" + be.getMessage());
System.out.println("打印异常信息:" );
be.printStackTrace();
}
}
}
自定义异常类在实际的开发中使用的非常普遍,它不但可以携带异常本身的信息,还可以根据业务的需要进行扩展,从而使开发人员能够获得更加丰富的异常信息。
java培训班:http://www.baizhiedu.com/java2019
注释:本文内容来自冰点IT