异常
Java的基本理念是”结构不佳的代码不能运行”
当在编译期无法找出所有的错误时,余下的错误必须在运行期解决
提出问题
现实世界中充满了不良的数据和带有问题的代码,由于程序的错误或一些外部环境的影响造成数据的丢失,用户就可能不再使用这个程序了。
概述
是什么
面对异常:
- 向用户报告错误
- 保存所有的工作结果
- 允许用户以妥善的形式推出程序
Java使用异常处理机制处理获异常
分类,各个分类是什么
异常分类
首先是Error类,它代表硬件上的错误,着运行时系统内部错误或者资源耗尽,或者内存不足等问题,在一些低配般服务器上很可能出现。应用程序不应该抛出该异常。
更需要关注的是Exception上的错误,它代表着软件方面的错误,也是我们程序员的领域。可以看到一个是RuntimeException类,另一个是IOException。
- IOException,即你在读取U盘,你把U盘拔了,这种搞事情的,或者IO错误等等,一般不可控,属于其他异常。
- 试图在文件尾部后读取数据、试图打开一个不存在的文件、试图根据给定字符串查找不存在的Class对象
- RuntimeException错误,这就是程序错误了
- 错误的类型转换、数组访问越界、访问null指针
而如果出现RuntimeException。则一定是程序员的问题。
因此派生于Error类与RuntimeException类的异常称为非受查异常。而其他的异常称为受查异常。编译器将检查是否为所有的受查异常提供了异常处理器。
可能的错误原因
为了能够在程序中处理异常情况,必须研究程序中可能会出现的错误和问题,以及哪类问题需要关注。
- 用户输入错误。
- 设备错误,即硬件可能被关掉了,打印机没有纸了
- 物理限制,即内存用完
- 代码错误,程序方法可能无法正确执行,例如数组越界等。
应用
适用性(作用)
应用场景
异常类
方法
Throwable()
Throwable(String msg)
带有特定的详细描述信息的对象String getMessage()
。获得Throwable对象的详细信息void initCause(Throwable exce)
,生成cause,值造成该异常的异常Throwable getCause()
。获得Cause变量,一般指造成该异常的异常Throwable printStackTrace()
可以访问堆栈轨迹的文本描述信息。StackTraceElement[] getStackTrace()
获得构造这个对象时调用堆栈的跟踪。可以在程序中分析这个数组。void addSuppressed(Throwable t)
为异常增加一个抑制异常,这出现在带资源的try语句中#
处理错误
如果由于出现错误而使得某些操作没有完成,程序应该:
- 返回一种安全状态,并让用户能够执行一些其他命令,或者
- 允许用户保存所有操作的结果,并以妥善的方式终止程序
而如此并不容易,因为引发错误条件的代码通常离那些能够让数据恢复到安全状态的代码很远。异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
在Java中,如果某个方法不能采用正确的途径完成它的任务,就可以通过另外一个路径退出方法,该情况下,方法不返回任何值,而是抛出一个封装了错误信息的对象。
这个方法会立即退出,并不返回任何职,此外调用这个方法的代码也将无法继续执行,而异常处理机制将开始搜索能够处理这种异常情况的异常处理器
声明受查异常
如果遇到了无法处理的情况,那么Java方法可以抛出一个异常,方法不仅需要告诉编译器将要返回什么值,还要告诉编译器可能发生什么错误。
当抛出异常后,运行时系统就会搜索异常处理器,以便知道如何处理该异常。
当遇到以下情况需要抛出异常
- 调用一个抛出受查异常的方法
- 程序运行过程中发现错误,并且利用throw语句抛出一个受查异常
- 程序出现错误,例如a[-1]
- Java虚拟机和运行时库出现的内部错误
不应该声明从RuntimeException继承的非受查异常。并且不需要声明从Error继承的错误,它们属于Java内部错误,我们对其没有控制力。
子类继承
如果子类覆盖了超类中的一个方法,子类方法中声明的受查异常不能比超类方法中声明的更通用。即子类可以抛出更特定的异常,或者不抛出异常。如果父类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
如何抛出异常
throw new EOFException()
对于一个已经存在的异常类,抛出非常容易,1.找到一个合适的异常类,2.创建这个类的一个对象,3.将对象抛出。
创建异常类
当遇到任何标准异常类都没有能够充分描述请求的问题,则需要定义自己的异常类。定义的类需要包含两个构造器:默认构造器、带有详细描述信息的构造器(超类的toString()会打印出这些信息)。
1 | class FileFormatException extends IOException{ |
捕获异常
捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,包括异常的类型和堆栈信息。
要捕获异常,则需要设置try catch语句块。如果在try语句块中的任何代码抛出了一个在catch语句中声明的异常类
- 程序将跳过try语句块的其余代码
- 程序将在执行catch语句中的处理器代码
- 如果没有抛出了没有声明的异常,则会立即退出。
throw or catch
编译器严格执行throws说明符,如果调用了一个抛出受查异常的方法就必须对它进行处理,或这继续传递
- 捕获那些知道如何处理的异常
- 将那些不知道怎样处理的异常继续传递。
- 如果传递异常则需要在方法首部增加throws
- 抛出异常是躲避异常的方法
- 异常会抛给调用该方法的方法,因此,调用方法的方法也要去抛出或者处理异常
再次抛出异常和异常链
在catch子句中可以抛出一个异常,这样的目的是改变异常的类型,如果开发了一个供其他程序员使用的子系统,那么用于表示子系统系统故障的异常类型可能会产生多种解释。
1 | try{ |
用带有异常信息文本的构造器进行构造新异常,有一种更好的处理方法是:
1 | try{ |
当上游捕获到异常,可以使用se.getCause()
获得原始异常。
finally
当代码抛出一个异常时,就会终止方法中的剩余代码处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有该方法知道,且资源在退出方法前必须回收。则产生资源回收问题。
因此使用finally语句进行资源回收操作,以及必须执行的操作,
- 对于try-catch-finally
- finally一定会执行,即使遇到了在try或者catch中遇到了return,也会先跳到finally里面执行,然后再跳转回到finally
- 多catch下,catch是顺序执行的,而且由于异常也是一个对象,因此应该将子类的catch放在最顶部,
分析堆栈轨迹元素
堆栈轨迹是一个方法调用过程的列表,包含了程序执行过程中方法调用的特定文字。当Java程序正常终止而没有捕获异常时,这个列表会显示出来
1 | java.lang.Exception: 出问题啦! |
最上面的是运行到的最后一个函数。即第三行代码调用了第六行,第六行调用了第10行
使用异常机制的技巧
- 异常处理不能代替简单测试
- 不要过分细化异常
- 利用异常层次结构
- 不要压制异常
- 在检测错误时,“苛刻”比放任好
- 不要羞于传递异常