Java笔记:异常

2015.03.04 | Comments

定义

在《java编程思想》中这样定义异常:阻止当前方法或作用域继续执行的问题。异常是Java程序设计中不可分割的一部分,如果不了解如何使用它们,那么我们只能完成很有限的工作。

分类

异常分为3种:

  • Error - 描述了Java运行系统中的内部错误以及资源耗尽的情况。应用程序不应该抛出这种类型的对象。如果这种内部错误出现,除了通知用户错误发生以及尽力安全的退出程序外,在其他方面是无能为力的。
  • 编译时异常 Exception - 它指出了合理的应用程序想要捕获的条件。Exception又分为两类:IOException和RuntimeException。由编程导致的错误,会导致RuntimeException异常。而其他错误原因导致的异常(例如因为I/O错误导致曾经运行正确的程序出错),都不会导致RuntimeException异常。
  • 运行时 RuntimeException - 表示运行时异常,不强制要求写出显示的捕获代码,但如果没有被捕获到,则线程会被强制中断

继承关系:

Throwable
|--Error 
|--Exception 
|--RuntimeException

从 RuntimeException 衍生出来的异常包括下面的问题:

  • 错误的类型转换
  • 数组越界访问
  • 试图访问空指针
  • 从 IOException 衍生出来的异常包括:
  • 试图从文件尾后面读取数据
  • 试图打开一个错误可是的 URL
  • 试图用一个字符串来构造一个 Class 对象,而与该字符串对应的类并不存在

异常的好处:

  • 1、将问题进行封装
  • 2、将正常流程代码和问题代码相分离,便于阅读。

处理异常机制

在Java应用程序中,异常处理机制为:抛出异常,捕捉异常。

  • 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

  • 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

任何 Java 代码都可以抛出异常,如:自己编写的代码、来自 Java 开发环境包中代码,或者 Java 运行时系统。无论是谁,都可以通过 Java 的 throw 语句抛出异常。

从方法中抛出的任何异常都必须使用 throws 子句。

捕捉异常通过 try-catch 语句或者 try-catch-finally 语句实现。

原则

编译时异常Exception,给了几条禁止的原则,他们是:

  • 1)不要直接忽略异常;
  • 2)不要用try-catch包住过多语句;
  • 3)不要用异常处理来处理程序的正常控制流;
  • 4)不要随便将异常迎函数栈向上传递,能处理尽量处理。

向上传播异常:

如果不能用上述恢复措施,就检查能不能向上传播,什么情况下可以向上传播呢?有多种说法,一种说法是当本方法恢复不了时,这个说法显然是错误,因为上层也不一定能恢复。另外还有两种说法是:1.当上层逻辑可以恢复程序时;2.当本方法除了打印之外不能做任何处理,而且不确定上层能否处理。这种两种说法都是正确的,但还不够,因为也有的情况,明确知道上层恢复不了也需要上层处理,所以我认为正确的做法是:当你认为本异常应该由上层处理时,才向上传播。

何时选用编译时异常:

  • 1、如果调用者可以恢复此异常情况
  • 2、如果调用者不能恢复,但能做出有意义的事,如转译等。如果你不确定调用者能否做出有意义的事,就别使编译时异常,免得被抱怨。
  • 3、应尽最大可能使用编译时异常来代替错误码,这条也是编译时异常设计的目的。

另外,必须注意使用编译时异常的目的是为了恢复执行,所以设计异常类的时候,应提供尽量多的异常数据,以便于上层恢复,比如一个解析错误,可以在设计的异常类写几个变量来存储异常数据:解析出错的句子的内容,解析出错句子的行号,解析出错的字符在行中的位置。这些信息可能帮助调用恢复程序。

注意事项

当使用多个 catch 语句块来捕获异常时,需要将父类的 catch 语句块放到子类型的 catch 块之后,这样才能保证后续的 catch 可能被执行,否则子类型的 catch 将永远无法到达,Java 编译器会报编译错误。

如果 try 语句块中存在 return 语句,那么首先会执行 finally 语句块中的代码,然后才返回。

如果 try 语句块中存在 System.exit(0) 语句,那么久不会执行 finally 语句块的代码了,因为 System.exit(0)会终止当前运行的 JVM。程序在 JVM 终止前结束执行。

常见面试题目

  • 1、error和exception有什么区别

error 表示系统级的错误,是java运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java虚拟机抛出的。

exception 表示程序需要捕捉、需要处理的异常,是由与程序设计的不完善而出现的问题,程序必须处理的问题

  • 2、运行时异常和一般异常有何不同

Java 提供了两类主要的异常:runtimeException 和 checkedException

一般异常(checkedException)主要是指 IO 异常、SQL 异常等。对于这种异常,JVM 要求我们必须对其进行 cathc 处理,所以,面对这种异常,不管我们是否愿意,都是要 写一大堆的 catch 块去处理可能出现的异常。

运行时异常(runtimeException)我们一般不处理,当出现这类异常的时候程序会由虚拟机接管。比如,我们从来没有去处理过 NullPointerException,而且这个异常还是最 常见的异常之一。

出现运行时异常的时候,程序会将异常一直向上抛,一直抛到遇到处理代码,如果没有 catch 块进行处理,到了最上层,如果是多线程就有 Thread.run()抛出,如果不是多线程 那么就由 main.run() 抛出。抛出之后,如果是线程,那么该线程也就终止了,如果是主程序,那么该程序也就终止了。

其实运行时异常的也是继承自 Exception,也可以用 catch 块对其处理,只是我们一般不处理罢了,也就是说,如果不对运行时异常进行 catch 处理,那么结果不是线程退出就是 主程序终止。

如果不想终止,那么我们就必须捕获所有可能出现的运行时异常。如果程序中出现了异常数据,但是它不影响下面的程序执行,那么我们就该在catch块里面将异常数据舍弃, 然后记录日志。如果,它影响到了下面的程序运行,那么还是程序退出比较好些。

  • 3、Java 中异常处理机制的原理

Jav a通过面向对象的方式对异常进行处理,Java 把异常按照不同的类型进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它都是 Throwable 或其子类的实例。当一个方法出现异常后就会抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并对异常进行处理。Java 的异常处理是通过5个 关键词来实现的:try、catch、throw、throws、finally。

  • try:用来指定一块预防所有异常的程序
  • catch:紧跟在try后面,用来捕获异常
  • throw:用来明确的抛出一个异常
  • throws:用来标明一个成员函数可能抛出的各种异常
  • finally:确保一段代码无论发生什么异常都会被执行的一段代码。

  • 4、你平时在项目中是怎样对异常进行处理的。

1)尽量避免出现 runtimeException。例如对于可能出现空指针的代码,带使用对象之前一定要判断一下该对象是否为空,必要的时候对 runtimeException也进行 try catch 处理。

2)进行 try catch 处理的时候要在 catch 代码块中对异常信息进行记录,通过调用异常类的相关方法获取到异常的相关信息。

  • 5、final、finally、finalize的区别

(1)、final 用于声明变量、方法和类的,分别表示变量值不可变,方法不可覆盖,类不可以继承

(2)、finally 是异常处理中的一个关键字,表示 finally{} 里面的代码一定要执行

(3)、finalize 是 Object 类的一个方法,在垃圾回收的时候会调用被回收对象的此方法。

参考资料


原创文章,转载请注明: 转载自JavaChen Blog,作者:JavaChen
本文链接地址:http://blog.javachen.com/2015/03/04/note-about-java-exception.html
本文基于署名2.5中国大陆许可协议发布,欢迎转载、演绎或用于商业目的,但是必须保留本文署名和文章链接。 如您有任何疑问或者授权方面的协商,请邮件联系我。