5.1 异常分类
如图所示,Throwable是所有异常的父类,往下划分为两大门派Error和Exception。
Error类是指Java运行时系统的内部错误和资源耗尽错误,应用程序不会抛出这种类型的对象。如果出现这种错误,听天由命...
Exception又分解为两个分支,可以看做是受查异常和非受查异常或者是其他分支和RuntimeException;程序错误导致:错误的类型转化、数组访问越界、空指针等都属于运行时异常,“如果出现RuntimeException异常,那么就一定是你的问题”很有道理。受查异常必须被处理,否则编译器报错。
Java语言将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。
public class ExceptionTest {
/**
* 第一个疑问点?
* 当在方法内部throw之后,有时方法会有throws,有时没有?
* 其实就是受查和非受查异常区别:
* 一般我们处理的都是受查异常
*/
public void writeData() throws IOException {
// IOException属于受查异常,必须处理,要么抛出要么捕获
//write...
throw new IOException();
/*
try {
throw new FileNotFoundException();
} catch (IOException e) {
e.printStackTrace();
}
*/
}
public void uncheck() {
// ClassCastException属于RuntimeException非受查异常
// 出现这种错误,就是代码有问题(数组下标越界..),
// 这种错误一般是在运行期发现的,由JVM抛出,我们人为不需要处理,
// 如果我们知道数组下标越界,为什么还不修改代码。
throw new ClassCastException();
// throw new RuntimeException();
}
}
常用非受查异常
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
常用受查异常
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
Throwable方法
序号 | 方法及说明 |
---|---|
1 | public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 | public Throwable getCause() 返回一个Throwable 对象代表异常原因。 |
3 | public String toString() 使用getMessage()的结果返回类的串级名字。 |
4 | public void printStackTrace() 打印toString()结果和栈层次到System.err,即错误输出流。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 | public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
5.2 异常的处理
异常的处理要么捕获、要么抛出(异常声明)。
5.2.1 受查异常声明
受查异常的声明也是一种处理方案,利用throws声明方法可能抛出的异常,在调用此方法时必须对异常进行处理,要么捕获,要么继续向上级传递抛出,如果主方法上使用了throws抛出,则交给JVM处理,。
在编写方法时,不必将所有可能出现的异常都进行声明(throws)。什么异常使用throws子句进行声明,什么时候不需要,遵循下面原则:
- 调用一个抛出受查异常的方法
- 程序运行过程中发现错误,并且利用throw语句抛出的一个受查异常
- 不需要声明Java的内部错误,即从Error继承的错误。我们对其没有任何控制能力
一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么应该避免发生(RuntimeException)。
throw和throws的区别?
throw和throws都是在异常处理中使用的关键字,区别如下:
- throw:指的是在方法中人为抛出一个异常对象(这个异常对象可能是自己实例化或者抛出已存在的);
- throws:在方法的声明上使用,表示此方法在调用时必须处理异常。
5.2.2 异常捕获
Java提供了try、catch、finally
三个关键字来捕获处理异常。
(1)try后必须要有一个catch或者finally块。
(2)catch依附于try而存在,有catch必有try,同一个catch语句可以捕获多个异常。
(3)同级异常,catch不分前后,上下级(父子关系),上级放后(其实就是让异常的信息更具体)。
(4)try/catch是最常用的捕获异常的一种方案,finally非必须。
(5)无论是否发生异常或者try语句块存在return,finally里面的代码最终都执行。若finally里面也存在return,那么会覆盖catch的值。编写资源回收的代码通常在finally语句块中
public void readData() {
FileInputStream in = null;
//-----------示例一-----------
try {
in = new FileInputStream("/usr/data");
int read = in.read();
// FileNotFoundException 是IOException的子类,必须放在父类之前
// catch可以捕获多个异常,前提是不存在子类关系。异常之间的类无关联
} catch (FileNotFoundException | UnknownElementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// finally最终都会执行
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//-----------示例二-----------
// 父类可以捕获其任何子类的异常
// 通过捕获更大范围的异常,可以减少catch语句的数量
try {
in = new FileInputStream("/usr/data");
int read = in.read();
// IOException可以捕获其子类的异常
} catch (IOException e) {
e.printStackTrace();
} finally {
// finally最终都会执行
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//-----------示例三-----------
// JDK1.7新增特性 (try-with-resources)带资源的try catch
// 假设资源属于AutoCloseable类型,try(Resources res=...){},当try语句块执行完毕之后
// 会自动调用res.close方法
try(FileInputStream fin = new FileInputStream("/user/data")) {
int read = fin.read();
} catch (IOException e) {
e.printStackTrace();
}
}
小结
// 向下面那样,finally里面又有try..catch很繁琐,对于处理IO流的关闭,如果项目中有很多这种代码块时,我们不得不考虑优化了
(1)try-with-resources
(2)自定义类继承自AutoCloseable接口,重写close方法,将异常提升到这里面。
finally {
// finally最终都会执行
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
5.3 自定义异常类 断言
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
- 一个无参构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数。
- 一个带有String参数和Throwable参数,并都传递给父类构造函数
- 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
编写自定义异常类,可以参考java源码,例如IOException
package java.io;
public class IOException extends Exception {
static final long serialVersionUID = 7818375828146090155L;
public IOException() {
super();
}
public IOException(String message) {
super(message);
}
public IOException(String message, Throwable cause) {
super(message, cause);
}
public IOException(Throwable cause) {
super(cause);
}
}
如何优雅的处理异常?