Java 异常类与捕获异常

禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!

异常引发

我们肯定见过堆栈轨迹,也就是程序执行过程中某个特定点上挂起的方法调用列表。当程序因为未捕获的异常而终止时,就会显示堆栈轨迹。异常会因为程序本身或者错误的操作等情况引发,这会导致操作无法完成或者数据丢失等不良后果。为了避免异常带来的不良后果,程序应该在异常发生时保证:

  1. 向用户通知错误;
  2. 让用户返回到一种安全状态,让用户执行其他的命令;
  3. 允许用户保存工作结果,以妥善的方式终止程序。

异常处理的任务就是将控制权从产生异常的地方,转移到能够处理异常的异常处理器。异常处理通常需要考虑:

  1. 用户输入错误,当需要用户进行输入时,用户可能不会按照程序的要求来输入;
  2. 设备错误,程序调用的硬件出现问题;
  3. 物理限制,例如程序所需的存储空间耗尽;
  4. 代码错误,程序有的方法编写错误,引发异常。

传统的方式是返回一个特殊的错误码,方法根据错误码进行分析。或者是返回 null 引用,但无论是空引用和错误码,这些都是程序编写中可能会使用的值。因此在很多情况下,方法并不返回任何值,而是抛出一个封装了错误信息的对象。

异常类

异常对象都派生自 Throwable 类的一个类实例,所有的异常都是由 Throwable 继承而来,Throwable 类被分解为 Error 和 Exception 2 个类。Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误,这类错误抛出的情况很少,而且很多时候都无能为力。
Exception 层次结构又分为 2 个分支,一个分支派生于 RuntimeRxception,这类异常包括了编程错误导致的异常。另一个分支包含其他异常,但是程序本身是没有问题的,例如数组范围越界就是个 RuntimeRxception 异常,而打开一个不存在的文件就属于其他异常。可以说,如果出现 RuntimeRxception 异常,那一定是程序员的错。

派生于 Error 类和 RuntimeException 类的所有异常为非检查型异常,其他所有异常成为检查型异常。

声明异常

当程序遇到异常时,方法需要抛出一个异常,方法需要告诉编译器可能会返回什么样的异常。声明异常之后的方法发生异常时,方法将抛出一个异常类对象,同时系统就会开始搜索处理异常对象的异常处理器
可以使用 throws 关键字进行异常规范声明,展示这个方法可能抛出的异常。

public Image loadImage(String str) throws IOExcpetion

如果一个方法可能抛出多种异常,则需要在方法的首部列出所有的异常类,异常类之间用逗号隔开。

public Image loadImage(String str) throws FileNotFoundException,IOExcpetion

Error 和 RuntimeException 的非检查型异常都不需要被声明,因为这是程序员一开始就需要避免的事情。因此一个方法需要声明所有可能抛出的检查型异常,而不是非检查型。

抛出异常

如果一个类的一个方法声明会抛出一个异常,而这个异常是某个特定类的实例,则这个方法抛出的异常可能属于这个类,也可能属于这个类的任意一个子类。一旦异常被抛出,这个方法就不会返回到调用者。声明异常之后应该如何显式地抛出异常?使用 throw 关键字抛出异常对象,异常对象同样需要 new。

throw new ExceptionName()

异常类中可能有自带一个字符串参数的构造器,通过这个构造器可以给出异常的描述信息。例如 IllegalArgumentException 异常可以通过它的构造器附带说明信息:

throw new IllegalArgumentException("这是个不合法的参数异常");

创建异常类

程序可能遇到某种特殊的异常,这种异常用现有的异常类可能很难描述,此时可以自定义一个异常类。自定义的异常类派生于 Excpetion 类或者 Excpetion 类的某个子类,自定义的类应该包括默认构造器和包含详细描述信息的构造器。例如:

class FileFormatException extends IOException
{
      public FileFormatException() {}
      public FileFormatException(String message) {
            super(message);
      }
}

自定义异常类之后,就可以在方法中声明异常并在方法中抛出。

捕获异常

光把异常抛出还不够,如果没有在任何地方捕获异常,程序就会终止并在控制台打印异常信息。

try/catch 语句块

使用 try 和 catch 关键字可以捕获异常,try/catch 语句块应该被放置在可能产生异常的地方,使用 try/catch 的语法如下:

try{
   code
}
catch(ExceptionName e){
   code
}

如果 try 语句中的任何代码抛出了 catch 子句指定的一个异常类,则程序会跳过 try 语句块的剩余代码,转去执行 catch 子句中的处理器代码。如果 try 语句块的代码没有抛出任何异常,则 catch 子句的代码会被自动跳过。如果方法中任何代码抛出了 catch 子句中没有生命的一个异常类型,这个方法就会自动退出。例如:

import java.io.*;

public class ExcepTest{
 
   public static void main(String args[]){
      try{
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      }
      catch(ArrayIndexOutOfBoundsException e){
         System.out.println("Exception thrown  :" + e);
      }
   }
}

捕获多个异常

一个 try 代码块后面跟随多个 catch 代码块,通过对每个异常类型都是用一个单独的 catch 子句就可以实现异常的多重捕获

try{
   code
}
catch(异常类型 1 异常的变量名 1){
  code
}
catch(异常类型 2 异常的变量名 2){
  code
}
catch(异常类型 3 异常的变量名 3){
  code
}

同一个 catch 子句可以捕获多个异常,如果 2 种异常捕获后采取的动作是相同的,可以用 “|”进行连接。

try{
   code
}
catch(异常类型1 | 异常类型2 异常的变量名){
  code
}

异常链

可以在 catch 子句中再次抛出异常,这种做法可以改变异常的类型。例如开发了一个供其他人使用的子系统,就可以用这个方式指示子系统能识别的异常类型。

try{
   code
}
catch(ExceptionName e1){
   var e2 = new ExceptionName();
   throw e2;
}

捕获到这种异常时,可以使用 getCause() 方法获取原始异常,这样子系统不仅能获取异常,也不会丢失异常的原始信息。

finally 子句

当代码抛出异常时,方法中的剩余代码就会立即停止并退出。这种情况有时候会加重遇到的麻烦,例如方法在退出前调用了一些本地资源,则这些资源就没有办法得到清理。finally 关键字用来创建在 try 代码块后面执行的代码块,无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。语法为:

try{
  code
}
catch(异常类型 异常的变量名){
  code
}
finally{
  code
}

try 语句可以只有 finally 子句,而忽略 catch 子句。不过 finally 子句也可能有异常,可以使用嵌套的 try 子句来捕获异常,这种方法不仅保险且功能更强。

try{
      try{
            code
      }
      finally{
            code
      }
}
catch(异常类型 异常的变量名)
{
      code
}

try-with-Resources 语句

假设资源属于一个实现了 AutoCloseable 接口的类,Java7 之后可以使用 try-with-resources 语句来代替 finally 子句。try-with-resources 语句可以在 try 块退出是,自动调用 close() 方法关闭资源。

try (Resources res = ……){
      work with res
}

捕获还是仅抛出?

使用 try/catch 语句块可以在 catch 处理异常,但是是否是所有异常都值得补获?还有一种策略是什么都不做,把异常传递给方法调用者,让调用这个方法的程序员自己思考要怎么做。如果想这么搞,就必须声明方法可能抛出的异常。如果调用了一个抛出检查型异常的方法,就必须处理这个异常,或者继续传递这个异常。
一般的经验是,捕获你知道如何处理的异常,传播你不知道怎么处理的异常。

异常使用技巧

  1. 异常处理不能替代简单的测试,只在异常情况下使用异常就好。
  2. 不要过分细化异常,将整个任务放在一个 try 语句块就行。
  3. 充分利用异常层次结构,不要只抛出 RuntimeException 异常,应该使用更合适的异常类,不要捕获 Throwable 异常,因为这难以理解和维护。
  4. 不要压制异常,对于异常需要重视并处理,不能够随意忽视。
  5. 检测错误时,应该返回一个能够具体描述异常情况的异常。
  6. 有时候不需要捕获所有抛出的异常,传递异常也是很好的做法。

参考资料

菜鸟教程
《Java 核心技术 卷Ⅰ》,[美]Cay S.Horstmann 著,林琪 苏钰涵 等译,机械工业出版社

原文地址:https://www.cnblogs.com/linfangnan/p/13474515.html