异常分类 – 在Java中,异常对象都是派生于Throwable类的实例。
下面我们来看一下异常层次结构示意图。
Throwable分解为两个分支:Error 和 Exception
Error类层次结构描述了Java运行时系统内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对。如果出现了这样的内部错误,除了通知给用户,并尽可能使程序安全终止以外,再也无能为力。
Exception层次又分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常【非RuntimeException】。
RuntimeException:这类程序不可预知,我们应该在编写程序时经可能避免。
非RuntimeException:可以预知,编译器可以检测的异常。我们可以将异常
抛出 或者进行 try{}catch(){}
处理。否则无法进行编译。
常见的Error
StackOverflowError ---- 栈内存溢出
ThreadDeath ---- 线程死锁
OutOfMemoryError ---- 内存溢出错误
常见的RuntimeException
常见的非RuntimeException
Java语言贵方将派生于Error类或RuntimeException类的所有异常称为非受查异常(unchecked)异常,所有其他的异常称为受查(checked)异常。编译器将核查是否为所有受查异常提供了异常处理器。
Java异常处理机制
抛出异常:声明受查异常交由运行时系统。 运行时系统负责寻找处置异常的代码并执行 。
捕获异常:寻找合适的处理器处置异常,否则程序终止运行。
声明受查异常
一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生声明错误。方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。举例 -->
public void upload(HttpServletRequest request) throws FileUploadException {}
从这个声明我们可以看出,方法将使用给定的HttpServletRequest参数进行操作,但是有可能会抛出一个受查异常FileUploadException。
以下两种情况时应该抛出异常:
调用一个抛出抽查异常的方法。
程序运行过程中发现错误,并利用throw语句抛出一个受查异常。
如果一个方法有可能抛出多个受查异常,那么必须在方法首部列出所有的异常类。
public static void method(int num) throws FileNotFoundException, IOException {}
最好不要所有异常都使用 Exception 进行声明,这样会降低可读性,太过于抽象,不利于分析。应该将产生的异常类型进行明确的定义。
public static void method(int num) throws Exception{}
总之一个方法必须声明所有可能抛出的异常,而非受查异常要么是不可控制(Error)要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
自定义异常
在程序中可能遇到任何标准异常类都没有办法描述的异常,这时候我们就需要创建自己的异常,我们只需要常见一个派生于Exception的类即可。下面我定义了一个默认构造器和一个详情构造器(用于打印详情信息)
//自定义一个异常类
public class MyException extends Exception{
public MyException() {
System.out.println("您的程序发生异常啦...");
}
public MyException(int exceptionState) {
if (exceptionState == 0) {
System.out.println("程序错误原因为 异常终止");
}
if (exceptionState == 1) {
System.out.println("程序错误原因为 数组越界");
}
}
}
public class ExceptionTest01 {
public static void main(String[] args) throws MyException {
methodOne("A");
}
public static void methodOne(String name) throws MyException{
if ("A".equals(name)) {
throw new MyException(0);
}
if ("B".equals(name)) {
throw new MyException(1);
}
}
}
---------------------------------------输出结果---------------------------------------
程序错误原因为 异常终止
Exception in thread "main" throwablepackage.MyException
at throwablepackage.ExceptionTest01.methodOne(ExceptionTest01.java:11)
at throwablepackage.ExceptionTest01.main(ExceptionTest01.java:6)
捕获异常
想要捕获一个异常,必须设置try/catch语句块。示例
try{
Code
}catch(ExceptionType e){
ExceptionHandler
}
如果程序在try语句块中抛出了一个在catch子句中说明的异常,那么程序将跳过try语句块中的其余代码,执行catch子句中的代码。
如果在try语句块中的代码没有抛出异常,则程序跳过catch子句。
如果方法中抛出一个没有在catch子句中声明的异常类型,那么这个方法就会立刻退出。
捕获多个异常
既然我们可以抛出多个异常,那么肯定也可以捕获多个异常。在一个try语句块中可以捕获多个异常,对不同类型的异常进行不同的处理。
try{
Code
}catch(ExceptionType e){
Handler1
e.getClass.getName();
}catch(ExceptionType 2){
Handler2
e.getMessage();
}
e.getMessage用于获取对象的详情信息。
合并catch子句
try{
}catch(ExceptionType | ExceptionType e){
...
}
注意catch中声明的异常类型彼此之间不能存在子类关系。捕获多个异常可以使你的代码看起来更加简洁,并且会更搞笑,但是相对来说难以编写。至少我到现在并没有用到过。
finally
当代码抛出异常后,就会退出这个方法的执行。但是如果有资源在退出方法之前必须被回收,那该怎么办呢?使用finally子句可以在任何情况下保证代码的执行。 不管是否有异常被捕获,finally子句中的代码都会被执行。
public static void main(String[] args) {
method();
}
static void method() {
try {
int num = 1/0;
System.out.println("try代码块继续执行。。。");
}catch (ArithmeticException e){
System.out.println("算数运算异常。。。");
}finally {
System.out.println("finally子句继续执行。。。");
}
}
----------------------------------------输出结果--------------------------------------
算数运算异常。。。
finally子句继续执行。。。
流程:算数运算【num = 1/0】出现异常–>被catch捕获–>执行catch子句代码–>执行finally子句
我们在很多使用都是使用finally子句继续资源的关闭。
堆栈轨迹
堆栈轨迹是一个方法调用过程的列表。它包含了程序执行过程中方法调用的特定位置。
catch (ArithmeticException e){
e.printStackTrace();
}
---------------------------------------输出结果---------------------------------------
java.lang.ArithmeticException: / by zero
at myframe.com.mjw.example.Demo01.method(Demo01.java:10)
at myframe.com.mjw.example.Demo01.main(Demo01.java:5)
我们通过打印出的堆栈轨迹可以明确的知道我们是在程序第几行捕获了异常。可以使我们快速的定位和修改错误。