异常处理的任务就是将控制权从产生错误的地方转移到能够处理这种情况的错误处理器。为了能够处理程序中的异常情况,必须考虑到程序中可能会出现的错误和问题。那么需要考虑哪些问题呢?
- 用户输入错误。除了那些不可避免的键盘输入错误外,有些用户喜欢自行其是,而不遵守程序的要求。例如,假设有一个用户请求连接一个URL,而这个URL语法却不正确。你的代码应该对此进行检查,如果没有检查,网络层就会报错。
- 设备错误。硬件并不总是让它做什么,它就做什么。打印机可能被关掉了。网页可能临时性地不能浏览。在任务的处理过程中,硬件经常会出现问题。例如,打印机在打印过程中可能没有纸了。
- 物理限制。磁盘已满,你可能已经用尽了所有可用的存储空间。
- 代码错误。程序方法有可能没有正确地完成工作。例如,方法可能返回了一个错误的答案,或者错误地调用了其他的方法。计算一个无效的数组索引,试图在散列表中查找一个不存在的记录,或者试图让一个空栈执行弹出操作,这些都属于代码错误。
1.1 异常分类
在Java程序设计语言中,异常对象都是派生于Throwable
类的一个类实例。如果Java 中内置的异常类不能满足需求,用户还可以创建自己的异常类。
需要注意的是,所有的异常都是由Throwable
继承而来,但在下一层立即分解为两个分支: Error
和 Exception
。
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。你的应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通知用户,并尽力妥善地终止程序之外,你几乎无能为力。这种情况很少出现。
Exception 分解为两个分支:
- RuntimeException:属于由编程错误导致,除了 RuntimeException 异常外,其他的都属于 IOException 异常
- 错误的强制类型转换
- 数组访问越界
- 访问
null
指针
- IOException:属于
I/O
错误导致的- 试图超越文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
Java语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为非检查型 (unchecked) 异常,所有其他的异常称为检查型 (checked) 异常。编译器将检查你是否为所有的检查型异常提供了异常处理器。
1.2 声明检查型异常
如果遇到了无法处理的情况,Java方法可以抛出一个异常。这个道理很简单:方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如,一段读取文件的代码知道有可能读取的文件不存在,或者文件内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出 IOException 类的异常。
例如:public FileInputStream(String name) throws FileNotFoundException
需要在自己编写的方法中用 throws 子句声明异常,以及要用 throws 子句声明哪些异常,需要记住在遇到下面4种情况时会抛出异常:
- 调用了一个抛出检查型异常的方法,例如,FileInputStream 构造器
- 检测到一个错误,并且利用throw语句抛出一个检查型异常(下一节将详细介绍throw语句)
- 程序出现错误,例如,a[-1]=0会抛出一个非检查型异常(这里会抛出 ArrayIndex0utOfBoundsException)
- Java虚拟机或运行时库出现内部错误。
如果没有处理器捕获这个异常,当前执行的线程有可能终止。
总之,一个方法必须声明所有可能抛出的检查型异常,而非检查型异常要么在你的控制之外(Error),要么是由从一开始就应该避免的情况所导致的(RuntimeException)。如果你的方法没有声明所有可能发生的检查型异常,编译器就会发出一个错误消息。
1.3 如何抛出异常
现在假设在程序代码中发生了糟糕的事情。一个名为readData的方法正在读取一个文件,文件首部包含以下信息,承诺文件长度为1024个字符:Content-length: 1024
然而,读到733个字符之后文件就结束了。你可能认为这是一种不正常的情况,希望抛出一个异常。
下面是抛出这个异常的语句:throw new EOFException();
或者
:::info
var e = new EOFException();
throw e;
:::
下面将这些代码放在一起:
String readData(Scanner in) throws EOFEception
{
...
while(...)
{
if(!in.hasNext()) // 遇到 EOF 错误
{
if (n < len)
{
throw new EOFEception();
}
}
...
}
return s;
}
在前面已经看到,如果一个已有的异常类能够满足你的要求,抛出这个异常非常容易。在这种情况下:
1.找到一个合适的异常类。
2.创建这个类的一个对象。
3.将对象抛出。
一旦方法抛出了异常,这个方法就不会返回到调用者。也就是说,不必操心建立一个默认的返回值或错误码。
1.4 创建异常类
你的代码可能会遇到任何标准异常类都无法描述清楚的问题。在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception的类,或者派生于Exception的某个子类,如IOException。习惯做法是,自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器(超类Throwable的toString方法会返回一个字符串,其中包含这个详细信息,这在调试中非常有用
class FileFormatException extends IOEception
{
public FileFormatException(){}
public FileFormatException(String gripe){
super(gripe);
}
}
然后就可以抛出自己定义的异常
String readData(Scanner in) throws FileFormatException
{
...
while(...)
{
if(!in.hasNext()) // 遇到 EOF 错误
{
if (n < len)
{
throw new FileFormatException();
}
}
...
}
return s;
}