Java异常机制
Java异常分类
异常表明程序运行发生了意外,导致正常流程发生错误,例如数学上的除0,打开一个文件但此文件实际不存在,用户输入非法的参数等。在C语言中我们处理这类事件一般是将其与代码正常的流程放在一起,通过判断条件让程序作出相应的操作,例如添加提示信息然后让程序返回一个错误码。在Java中引入了异常处理,可以帮助我们将程序的正常流程和错误处理分开。Java中所有的异常都继承自java.lang.Exception类。Throwable类又包含两个直接的子类,分别为Error和Exception,从这两个子类又分别生出一些新的异常类,如图所示:
Exception是从任何Java类库的方法(包括标准类库的方法和自己定义的类方法)以及运行时抛出异常的基类。它可以分为运行时异常(RuntimeException)和检查异常(Checked Exception)两种。对于检查异常Java编译器会检查它,编译器要求必须捕获(使用try-catch)或声明抛出这种异常(使用throws语句声明抛出),除了RuntimeException外的其他异常均为检查异常。对于RuntimeException在默认情况下会自动处理,例如除0操作,访问非法的数组下标等,所以一般不用捕获RuntimeException,但是如果是自己定义的异常则需要抛出。
Error表示程序中出现了严重的问题,一般需要终止程序,例如VirtualMachineError是Java虚拟机运行错误。Error与Exception的区别是Error和它的子类的对象不能被抛出,它也不需要抛出,因为一般遇到这类问题时都意味着程序需要中止。
Java内置异常
Java内置的一些异常都定义在java.lang中,标准的运行时异常一般是最常用的异常,因为java.lang是默认加载的,所以一般从RuntimeException继承来的异常都可以直接使用。Java内置的一些RuntimeException如下表:
异常 |
描述 |
ArithmeticException |
当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException |
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException |
试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException |
当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException |
抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException |
抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException |
在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException |
线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException |
指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException |
如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException |
当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException |
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException |
由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException |
此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException |
当不支持请求的操作时,抛出该异常。 |
CheckedException如下表所示:
异常 |
描述 |
ClassNotFoundException |
应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException |
当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException |
拒绝访问一个类的时候,抛出该异常。 |
InstantiationException |
当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException |
一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException |
请求的变量不存在 |
NoSuchMethodException |
请求的方法不存在 |
异常方法
Throwable类提供一些异常方法如下表所示:
序号 |
方法及说明 |
1 |
public String getMessage() |
2 |
public Throwable getCause() |
3 |
public String toString() |
4 |
public void printStackTrace() |
5 |
public StackTraceElement [] getStackTrace() |
6 |
public Throwable fillInStackTrace() |
Java异常处理
Java处理异常机制包括抛出异常和捕获异常,抛出异常是指当一个方法出现错误引发异常时,这个方法需要创建一个异常对象,异常对象包含异常类型和一些异常信息,使用throw关键字实现,如果一个方法没有捕获检查异常,则次方法必须使用throws关键字声明,throws关键字用在方法签名的尾部,当有多个异常需要声明时用","隔开。捕获异常从字面理解就是抓住异常,只有在异常抛出时才能被捕获,捕获异常一般用try…catch…finally语句实现,当存在try块时,catch和finally语句必须再少存在一个。
使用throws抛出异常
如果一个方法可能会抛出一个异常,但是没法处理这个异常,例如汽车这个类在飞驰时抛锚了,它自己只能抛出此异常,不能处理,而需要靠开汽车的人来想办法修理。下面方法抛出一个异常
public class MyClazz { public static int calc() throws ArithmeticException { int a = 0, b = 10; return b/a; } public static void main(String[] args) { try { calc(); } catch (ArithmeticException e) { System.out.println(“calc throws an ArithmeticException”); } } }
可以看到calc方法本身只是在方法声明时加上了throws,它并不处理异常,实际上由调用它的函数处理异常。当然方法本身也是可以处理异常的,对于throws的使用如果异常时Error或者RuntimeException或者他们的子类,则可以不用使用throws关键字声明,这些异常会被自动抛出。对于Checked Exception一定要在方法中声明异常,可以使用try…catch语句捕获或者使用throws关键字在声明将其抛出。对于抛出的异常如果自己没有处理能力则由其调用者处理。
使用throw抛出异常
throw用于在方法中抛出异常,语法格式为throw new ExceptionName,使用例子如下:
public class ExceptionTest { public static void f() throws Exception { int i; for (i = 0; i < 6; i++) { if (i == 5) { throw new Exception(); } System.out.println(i); } } public static void main(String[] args) { try { f(); } catch (Exception e) { e.printStackTrace(); } } }
运行之后可以看到,当i=5时会抛出异常,此时异常被捕捉到然后打印方法的堆栈信息,当抛出异常之后,它之后的语句就不会再执行了,所以此时5没有打印出来。
try…catch…finally语句使用
try块用来执行正常的流程,catch用来捕获相关的异常,finally语句表示无论是否有异常被捕获都会执行。
public class ExceptionTest { class Exception1 extends Exception {} class Exception2 extends Exception {} public void f() throws Exception { int i; for (i = 0; i < 6; i++) { if (i == 5) { throw new Exception(); } System.out.println(i); } } public void g() throws Exception1 { throw new Exception1(); } public void h() throws Exception2 { throw new Exception2(); } public static void main(String[] args) { ExceptionTest et = new ExceptionTest(); try { et.f(); et.g(); et.h(); } catch (Exception2 e) { e.printStackTrace(); } catch (Exception1 e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("ExceptionTest finally"); } } }
执行发现,当f()方法抛出异常后g()和h()方法均不会被执行,此时他们抛出的异常都被屏蔽掉了,但是finally语句块还是会被执行。在网上截取了一张try catch finally语句块的执行流:
自定义异常
在Java中可以自定义异常,编写异常时要注意几点:
- 所有异常必须是Throwable的子类;
- 如果是一个检查性异常必须继承Exception;
- 如果是一个运行时异常必须继承RuntimeException;
class Exception1 extends Exception { String message; Exception1(String message) { this.message = message; } public String getMessage() { return this.message; } } class Exception2 extends Exception { String message; public Exception2(String message) { this.message = message; } public String getMessage() { return this.message; } } public class ExceptionClazz { public static void f() { try { g(); throw new Exception1("f throw exception1"); } catch (Exception1 e) { System.out.println(e.getMessage()); } finally { System.out.println("f finally"); } } public static void g() { try { h(); throw new Exception2("g throw exception2"); } catch (Exception2 e) { System.out.println(e.getMessage()); } finally { System.out.println("g finally"); } } public static void h() { try { throw new Exception(); } catch (Exception e) { System.out.println("h throw a Exception"); } finally { System.out.println("h finally"); } } public static void main(String[] args) { f(); } }
最终打印为:
h throw a Exception
h finally
g throw exception2
g finally
f throw exception1
f finally