异常处理详解
〇、概述
在理想世界中,用户输入的数据格式永远都是正确的,选择打开的文件永远都是存在的,代码永远不会出现BUG,在现实世界中却充满了各种非法的数据和各种BUG。
对于异常情况,Java 使用了一种异常处理的错误捕获机制。
异常处理的任务就是将控制权从产生错误的地方转移到能够处理这种情况的错误处理器。
为了能够处理程序的异常情况,必须考虑程序中可能出现的错误和问题,需要考虑以下问题:
- 用户输入错误:用户输入数据不正确
- 设备错误:硬件出错
- 物理限制:磁盘、内存不足
- 代码错误:代码未正确的完成工作
一、异常分类
异常对象都继承自 Throwable,
Error: 描述了 Java 运行时系统的内部错误和资源耗尽错误。在应用程序代码中不应该抛出这个类型的异常。
RuntimeException: 由编程错误导致的异常
IOException: IO相关异常
Java 语言规范将继承 Error 类、 RuntimeException 类的所有异常称为非检查型异常。所有其他异常检查型异常。
二、异常的声明与抛出
1.异常的声明
如果遇到了无法处理的情况,Java 方法可以声明抛出一个异常。方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
以下是 FileInputStream 的一个真实示例:
// 在文件存在时,返回一个文件输入流,在文件不存在时,会抛出一个文件不存在异常
public FileInputStream(String name) throws FileNotFoundException{
...
}
声明异常的场景:
- 调用了一个抛出检查型异常的方法,例如调用FileInputStream构造方法。
- 检测到一个错误,并且利用 throw 语句抛出一个非检查型异常。
- 程序出现错误,如除零、数组越界异常的。
- Java 虚拟机或运行库出现内部错误。
一个方法声明抛出多个异常:
public Image loadImage(String path)throws FileNotFundException,EOFException{
...
}
不需要声明从Error继承的异常,任何代码都可抛出那个异常,我们无法控制。
2.异常的抛出
当遇到一些异常情况,无法继续执行下去时,需要抛出异常。
// 在方法内部需要抛出异常时,需要在方法首部声明异常。
public void method() throws Exception{
...
throw new Exception();
}
抛出异常的步骤:
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
三、异常的捕获
1. 捕获异常–try-catch
要想捕获一个异常,需要设计 try/catch 语句块
try{
code
more code
more code
}catch(ExceptionType e){
handler for this type
}
如果 try 语句块中的任何代码抛出了catch子句指定的一个异常类,那么:
- 程序将跳过try语句块的其余代码
- 程序将执行catch子句中的处理器代码
如果try语句中的代码没有抛出异常,程序将跳过catch子句
如果方法中的任何代码抛出了catch中没有声明的异常类型,那么这个异常就不会被捕获。
异常传递技巧:
- 要捕获你能处理的异常
- 继续传播你不能处理的异常
- 要传递异常,就必须在方法声明异常
2. 捕获多个异常–try-catch-catch-…
在一个try语句中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。要为每一个异常类型使用一个单独的catch子句:
try{
...
}catch (FileNotFundException e){
handler code
}catch (UnknowHostException e){
handler code
}catch (IOException e){
handler code
}
可使用 e.getMessage() 获取异常描述信息
在Java7及以上版本,可使用同一catch子句中捕获多个异常:
使用场景:当出现一种以上的异常,并且异常的处理流程相同时使用。
try{
...
}catch (FileNotFundException | UnknowHostException e){
// 这里的异常变量e,隐式的被final修饰,不可修改。
handler code
}catch (IOException e){
handler code
}
3. 再次抛出异常
再次抛出异常:可以在catch中抛出一个异常,通常需要改变异常的类型时,需要这样处理。
try{
...
}catch (Exception e){
throw new RuntimeException(e.getMessage);
}
4. finally 子句
作用:不管是否有异常被捕获,finally代码块,都将被执行,所以常用来释放资源。
FileInputStream in = new FileInputStream(...);
try{
more code
}catch (IOException e){
exception handler code
}finally{
in.close();
}
执行情况分析:
-
未发生异常:先try后finally
-
发生异常,被捕获:先try,发生异常时catch,
catch处理中出现异常,跳过catch后续处理,执行finally,并将异常返回给调用者。
catch处理中未出现异常,执行完catch代码,最后执行finally
-
发生异常,未被捕获:先try,后finally,并将异常返回给调用者。
注意不要再finally中使用return返回,会发生预想不到的错误。
5. try-with-Resources 语句
try代码块执行结束时,会自动调用close方法,等同于自动释放资源。
在try()中的资源类,必须实现AutoCloseable接口或Closeable的类,才可使用。
try(FileInputStream fis = new FileInputStream(...)){
...
}
四、创建自定义异常类
当遇到的异常情况,现有的异常类都无法描述时,我们需要创建自己的异常类。
我们需要定义一个派生于Exception的类,或者派生于Exception的某个子类。
习惯做法是:自定义的这个类应包含两个构造器,一个默认构造器,另一个包含详细描述信息的构造器。
public CustomerException extends RuntimeException(){
public CustomerException(){}
public CustomerException(String message){
super(message);
}
}
五、异常使用技巧
- 异常处理不能替代简单的测试
- 不要过分的细化异常
- 充分利用异常的层次结构
- 不要压制异常
- 在检测错误时,苛刻比放任更好
- 不要羞于传递异常
早抛出,晚捕获