Java之异常处理

238 阅读5分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

Java 异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常: 最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Java异常处理类

Java把异常当做是破坏正常流程的一个事件,当事件发生后,就会触发处理机制。

Java有一套独立的异常处理机制,在遇到异常时,方法并不返回任何值(返回值属于正常流程),而是抛出一个封装了错误信息的对象。下图是Java异常处理机制类层级结构图:

图2:Java异常处理机制类层级结构图

1 、Throwable

所有的异常对象都派生于Throwable类的一个实例。

1.1 Throwable有五种构造方法:

备注:

  • strack trace:堆栈跟踪。是一个方法调用过程列表,它包含了程序执行过程中方法调用的具体位置。

2.1.2 Throwable的所有成员方法:

备注:

  • 所有派生于Throwable类的异常类,基本都没有这些成员方法,也就是说所有的异常类都只是一个标记,记录发生了什么类型的异常(通过标记,编译期和JVM做不同的处理),所有实质性的行为Throwable都具备了。

  • 综上,在一个Throwable里面可以获取什么信息?

    • 获取堆栈跟踪信息(源代码中哪个类,哪个方法,第几行出现了问题……从当前代码到最底层的代码调用链都可以查出来)
    • 获取引发当前Throwable的Throwable。追踪获取底层的异常信息。
    • 获取被压抑了,没抛出来的其他Throwable。一次只能抛出一个异常,如果发生了多个异常,其他异常就不会被抛出,这时可以通过加入suppressed异常列表来解决(JDK7以后才有)。
    • 获取基本的详细描述信息

从图2可以看出,Throwable类只有两个直接继承者:Error和Exception。然后Exception又分为RuntimeException和Checked Exception。

2 Error

在Java中, 由系统环境问题引起的异常,一般都继承于Error类。

对于Error类:

  • 一般开发者不要自定义Error子类,因为它代表系统级别的错误。与一般的程序无关。
  • 在Java异常处理机制中,Error不强制捕获或声明,也就是不强制处理。因为程序本身对此类错误无能为力。一般情况下我们只要把堆栈跟踪信息记录下来就行。

3 Exception

在Java中,除了系统环境问题引起的异常,一般都继承于Exception类。Exception分为RuntimeException和Checked Exception。Checked Exception必须要捕获或声明。而RuntimeException不强制。

对于Exception类:

  • 如果你创建了一个异常类型,直接继承于Exception,那么这个异常类型将属于检查异常(Checked Exception)。

4 RuntimeException

在Java中,由于接口方法使用不当造成的异常,一般属于RuntimeException,也就是运行时异常。

对于RuntimeException:

  • 如果你调用服务方法的方式不正确,你应该马上修改代码,避免发生RuntimeException
  • 如果是用户方法调用你的方法的方式不正确,你应该立刻抛出RuntimeException,强制让使用者修正代码或改变使用方式,防止问题蔓延
  • 一般情况下,不要捕获或声明RuntimeException。因为问题在于你的程序本身有问题,如果你用异常流程处理了,反而让正常流程问题一直存在

捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

try
{
   // 程序代码
}catch(ExceptionName e1)
{
   //Catch 块
}

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。

1 声明异常

throws在方法后面声明异常(表示该方法可能会产生异常)

若声明的是受检查的异常 则在调用该方法时也要声明异常(一般开发工具会自动提示两种方法处理:继续throws或try-catch)

若声明的时非检查异常(指RuntimeException 以及他们的子类) 则不用做强制处理 可以不用声明

throw是在方法里面声明异常 一般是手动抛出,并且可以抛出更为明确的异常

2 自定义异常

一般自定义异常会继承Exception或RuntimeException类 但具体继承哪一个类呢 还是要视情况而定

若继承受检查的异常,会与方法耦合,若不抛异常就要try-catch 操作不够灵活

若继承RuntimeException 就相对上面的就简洁多了