1. 异常分类
在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。其是如果Java中的异常类不能满足需求,用户可以创建自己的异常类。
下图是Java异常层次结构的一个简化示意图。
从图上可以看出,所有的异常都是继承于Throwable类,但是在下一层立即分解为两个分支:Error和Exception。
(1)Error
Error描述了Java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的错误,如果出现了这样的内部错误,除了通告用户,并尽力使程序安全的终止之外,再也无能为力了。这种情况很少见。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,并且这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
(2)Exception
程序设计者应该关注的是Exception,这一层次异常又分为两个分支:IOException和RuntimeException。划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但是由于IO错误这类问题导致的异常属于IOException。
派生于RuntimeException的异常包括:
-
异常的运算条件,如一个整数除以0时
-
错误的类型转换
-
数组访问越界
-
访问空指针
派生于IOException的异常包括:
-
试图在文件尾部后面读取数据
-
试图打开一个不存在的文件
“如果出现RuntimeException异常则表明一定是你的问题”,这是一条相当有道理的规则。
注意:
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
|
(3)Checked Exception 与 UnChecked Exception
Java语言规范将派生于Error类或者RuntimeException类的所有异常称为未检查异常(UnChecked 异常),所有其他的异常(包括IOException)称为已检查异常(Checked 异常)。编译器将核查是否为所有的Checked 异常提供了异常处理器。
2. 声明已检查异常
如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
例如:一段读取文件的代码知道优肯风读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException异常。
方法应该声明所有可能抛出的已检查异常,这样可以反映出这个方法可能抛出哪类已检查异常。
例如:下面是标准类库中提供的FileInputStream类的一个构造方法的声明异常情况:
-
public FileInputStream(String name) throws FileNotFoundException
这个异常声明表示这个构造方法根据给定的字符串name正确情况下产生一个FileInputStream对象,但是也有可能抛出一个FileNotFoundException异常。如果抛出异常,方法不会初始化一个FileInputStream对象,而是抛出一个FileNotFoundException对象。抛出异常之后,运行时系统开始搜索异常处理器,以便知道如何处理 FileNotFoundException对象。
不是所有可能抛出的异常都必须进行声明,以下4种情况时记得抛出异常:
-
调用一个抛出已检查异常的方法,如FileInputStream构造方法
-
程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常
-
程序出现错误,例如,a[-1]=0会抛出一个下标越界的未检查异常
-
Java虚拟机和运行库出现的内部错误
对于前两种情况必须进行声明。
不需要声明Java的内部错误,即从Error继承的错误,任何程序代码都具有抛出那些异常的潜能,但是它们在我们的控制范围之外。同样也不应该声明从RuntimeException继承的那些未检查异常。
-
void Read(int index) throws ArrayIndexOutOfBoundsException // bad style
-
{
-
...
-
}
这些运行时错误完全在我们的控制范围之内,如果特别关注数组下标引发的错误,就会将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上。
总结:
-
一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。
-
如果一个方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误信息。
-
如果类中的一个方法声明会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就有可能抛出一个这个类,或者这个类一个子类的异常。
|
3. 如何抛出异常
假设有一个方法用来读取文件内容,给定的文本长度为1024,但是读到700个字符之后文件就结束了,我们认定这不是一种正常的情况,希望抛出异常。
首先决定应该抛出什么类型的异常(EOFException),知道之后抛出异常的语句如下:
-
// 第一种方法
-
throw new EOFException();
-
-
// 第二种方法
-
EOFException e = new EOFException();
-
throw e;
EOFException类还有一个含有一个字符串参数的构造方法,可以更加细致描述异常出现的状况:
-
String gripe = "未到指定长度,文件读取结束";
-
throw new EOFException(gripe);
对于一个已经存在的异常类,抛出异常过程:
-
找到一个合适的异常类
-
创建这个异常类的一个对象
-
将对象抛出
4. 创建异常类
在程序中,可能会遇到任何标准程序类都没有能够充分描述清楚的问题,这种情况下,我们需要创建我们自己的异常类。我们需要做的就是定义一个派生类于Exception,或者派生于Exception子类的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器,一个是带有详细描述信息的构造器。
-
public class FileFormatException extends Exception{
-
-
/**
-
*
-
*/
-
private static final long serialVersionUID = 1L;
-
// 默认构造器
-
public FileFormatException(){
-
-
}
-
// 带有详细描述信息的构造器
-
public FileFormatException(String gripe){
-
super(gripe);
-
}
-
}
现在我们可以抛出我们自己定义的异常类型了。
-
throw new FileFormatException;
5. 捕获异常
如果某个异常发生的时候没有任何地方进行捕获,那程序就会终止,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
-
package com.qunar.test;
-
public class ExceptionTest {
-
public static void main(String[] args) {
-
int a = 10;
-
int b = 0;
-
System.out.printf("%d / %d = %d",a,b,a/b);
-
System.out.println("测试结束...");
-
}
-
}
控制台信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.qunar.test.ExceptionTest.main(ExceptionTest.java:8)
从异常信息可以看出程序并没有运行完全,没有输出“测试结束...”,程序就终止,并且在控制台打印出异常信息。
要想捕获异常,必须使用try/catch语句块。
-
try{
-
code
-
more code
-
more code
-
}
-
catch (Exception e) {
-
handle for this type
-
}
(1)如果在try语句块中任何代码抛出一个在catch子句中说明的异常类,那么:
-
程序将跳过try语句块的剩余代码
-
程序将执行catch子句的处理器代码
(2)如果在try语句块中代码没有抛出异常,那么程序将跳过catch子句。
(3)如果方法中的任何代码抛出了一个在catch子句没有声明的异常类型,那么这个方法就会立刻退出。
-
package com.qunar.test;
-
-
public class ExceptionTest {
-
-
public static void main(String[] args) {
-
int a = 10;
-
int b = 0;
-
try{
-
System.out.printf("%d / %d = %d",a,b,a/b);
-
}
-
catch (ArithmeticException e) {
-
System.out.println("a / b b 不能等于0");
-
}
-
System.out.println("测试结束...");
-
}
-
}
运行结果:
看一个例子:(读取文本程序代码)
-
public void read(String name){
-
try{
-
InputStream inputStream = new FileInputStream(name);
-
int b;
-
while((b = inputStream.read()) != -1){
-
// ...
-
}//while
-
}
-
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
对于一个普通的程序来说,这样的处理异常基本上合乎情理,但是,通常最好的情况是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去处理,如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。
-
public void read(String name) throws IOException{
-
InputStream inputStream = new FileInputStream(name);
-
int b;
-
while((b = inputStream.read()) != -1){
-
// ...
-
}//while
-
}
如果调用了一个抛出已检查异常的方法,就必须对它进行处理或者将它继续进行传递。
出现了两种处理方式,那到底哪种方式更好呢?
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。如果想传递一个异常,就必须在方法添加一个throws说明符。仔细阅读Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是加到throws列表中。
6. 捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
-
try{
-
-
}
-
catch (FileNotFoundException e) {
-
// emergency action for missing files
-
}
-
catch (UnknownException e) {
-
// emergency action for unknown hosts
-
}
-
catch (IOException e) {
-
// emergency action for all other I/O problems
-
}
阅读(1989) | 评论(0) | 转发(0) |