如果一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了。为了避免这类事情发生,至少做到以下几点:
- 向用户通告错误;
- 保存所有的工作结果;
- 允许用户以妥善的形式推出程序。
对于异常情况,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。
处理错误
假设在一个Java程序运行期间出现了一个错误。这个错误可能是由于文件包含了错误信息,或者网络连接出现问题造成的,也有可能是因为使用无效的数组下标,试图使用一个没有被赋值的对象引用造成的。
由于出现错误而使得某些操作没有完成,程序应该:
- 返回到一种安全状态,并让用户执行一些其他命令;
- 允许用户保存所有操作的结果,并以妥善的方式终止程序
1.用户输入域错误
2.设备错误
3.物理限制
4.代码错误
在Java中,如果某个方法不能够采用正常的途径完成它的任务,就可以提供另外一个路径退出方法。在这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。这个方法将会立刻退出,并不返回任何值。取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)。异常具有自己的语法和特定的继承结构。
1.异常分类
Java中,异常对象都是派生于Throwable类的一个实例。如果Java中内置的异常不能够满足需求,用户可以创建自己的异常类。
所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception。
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。引用程序不应该抛出这种类型的对象。很少出现。
在Java中,需要关注Exception层次结构。又划分了两个分支,由程序错误导致的异常属于RuntimeException;而程序本身没问题,由于像I/O错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包含下面几种情况:
- 错误的类型转换
- 数组访问越界
- 访问null指针
不是派生于RuntimeException的异常包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定字符串查找Class对象,而这个字符串表示的类并不存在。
“如果出现RuntimeException异常,那么就一定是你的问题”
Java中将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,其他的异常称为受查(checked)异常。
2.声明受查异常
一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如:一段读取文件的代码知道有可能读取的文件不存在,或者内容为空,因此,试图处理文件的信息就需要通知编译器可能会抛出IOException类的异常
方法应该在首部声明所有可能抛出的异常。下面是标准类库中的FileInputStream类的一个构造器声明:
**public FileInputStream(String name) throws FileNotFoundException**
这个声明表示这个构造器将根据特定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果发生异常,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。
自己编写方法时,在遇到以下4种情况应该抛出异常:
- 调用一个抛出受查异常的方法,例如,FileInputStream构造器。
- 调用运行过程中发现错误,并且利用throw语句抛出一个受查异常
- 程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常
- Java虚拟机和运行时库出现的内部错误。
对于那些可能被他人使用的Java方法,应该根据异常规范在方法的首部声明这个方法可能抛出的异常。
class MyAnimation
{
.....
public Image loadImage(String s) throw IOExpection
{
.....
}
}
如果一个方法可能抛出多个受查异常类型,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
class MyAnimation
{
.......
public Image loadImage(String s) throw FileNotFoundException,EOFException
{
.....
}
}
不应该声明从RuntimeException继承的那些非受查异常
class MyAnimation
{
........
void drawImage(int i) throws ArrayIndexOutOfBoundsException //错误
{
......
}
}
这种运行时错误完全在我们的控制之下。如果特别关注数组下标引发的错误,就应该将更多的时间花费在修正程序的错误上,而不是说明这些错误发生的可能性上。
总之,一个方法必须声明所有可能的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有的可能发生的受查异常,编译器就会发出一个错误消息。
如果类中的一个方法声明将会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就又可能抛出这个类的异常,或者这个类的任意一个子类的异常。例如,FileInputStream构造器声明将有可能抛出一个IOException异常,然而并不知道具体是那种IOException异常。可能是IOExecption异常,也有可能是其子类的异常,FileNotFoundException。
3.如何抛出异常
一个名为readData的方法正在读取一个首部具有下列信息的文件:
Content-length: 1024
然而读到733个字符之后文件就结束了。我们认为这是一种不正常的情况,希望抛出一个异常。
首先要决定应该抛出什么类型的异常。将上述异常可以认为IOException:“在输入过程中,遇到一个未预期的EOF后的信号” 下面是抛出这个异常的语句:
throw new EOFException();
或者
EOFException e=new EOFException();
throw e;
下面将这些代码放在一起:
String readData(Scanner in) throws EOFException
{
......
while(.....)
{
if(!in.hasNext()) //EOF encountered
{
if(n<len)
throw new EOFExecption();
}
......
}
return s;
}
对于一个已经存在的异常类,将其抛出非常容易。
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
一旦方法抛出了异常,这个方法就不可能返回到调用者。不必为返回的默认值或错误代码担忧。
4.创建异常类
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。我们需要做的只是定义一个派生于IOExecption的类,或者派生于Exception子类的类。例如,定义一个派生于IOException的类,应该包含两个构造器,一个是默认构造器;另一个是带有详细描述信息的构造类。
class FileFormatException extends IOException
{
public FileFormatException() {}
public FileFormatExcepton(String gripe)
{
super(gripe);
}
}
现在就可以抛出自己定义的异常类型了
String readData(BufferedReader in) throws FileFormatException
{
.........
while(.....)
{
if(ch==-1) //调用异常
{
if(n<len)
throw new FileFormatException();
}
......
}
return s;
}
API java.lang.Throwable 1.0
Throwable()
构造一个新的Throwable对象,这个对象没有详细的描述信息
Throwable(String message)
构造一个新的Throwable对象,这个对象带有特定的详细描述信息。
String getMessage()
获得Throwable对象的详细描述信息