Chinaunix首页 | 论坛 | 博客
  • 博客访问: 89736
  • 博文数量: 19
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 158
  • 用 户 组: 普通用户
  • 注册时间: 2015-12-16 20:28
个人简介

Stay Hungry, Stay Foolish

文章分类

全部博文(19)

文章存档

2016年(9)

2015年(10)

我的朋友

分类: Java

2015-12-22 18:42:31

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类的一个构造方法的声明异常情况:

  1. public FileInputStream(String name) throws FileNotFoundException

这个异常声明表示这个构造方法根据给定的字符串name正确情况下产生一个FileInputStream对象,但是也有可能抛出一个FileNotFoundException异常。如果抛出异常,方法不会初始化一个FileInputStream对象,而是抛出一个FileNotFoundException对象。抛出异常之后,运行时系统开始搜索异常处理器,以便知道如何处理 FileNotFoundException对象。

不是所有可能抛出的异常都必须进行声明,以下4种情况时记得抛出异常:
  • 调用一个抛出已检查异常的方法,如FileInputStream构造方法
  • 程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常
  • 程序出现错误,例如,a[-1]=0会抛出一个下标越界的未检查异常
  • Java虚拟机和运行库出现的内部错误

对于前两种情况必须进行声明。

不需要声明Java的内部错误,即从Error继承的错误,任何程序代码都具有抛出那些异常的潜能,但是它们在我们的控制范围之外。同样也不应该声明从RuntimeException继承的那些未检查异常。

  1. void Read(int index) throws ArrayIndexOutOfBoundsException // bad style
  2. {
  3.  ...
  4. }

这些运行时错误完全在我们的控制范围之内,如果特别关注数组下标引发的错误,就会将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上。


总结:

  1. 一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。
  2.  如果一个方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误信息。
  3. 如果类中的一个方法声明会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就有可能抛出一个这个类,或者这个类一个子类的异常。


3. 如何抛出异常

假设有一个方法用来读取文件内容,给定的文本长度为1024,但是读到700个字符之后文件就结束了,我们认定这不是一种正常的情况,希望抛出异常。
首先决定应该抛出什么类型的异常(EOFException),知道之后抛出异常的语句如下:

  1. // 第一种方法
  2. throw new EOFException();
  3.  
  4. // 第二种方法
  5. EOFException e = new EOFException();
  6. throw e;

EOFException类还有一个含有一个字符串参数的构造方法,可以更加细致描述异常出现的状况:

  1. String gripe = "未到指定长度,文件读取结束";
  2. throw new EOFException(gripe);
对于一个已经存在的异常类,抛出异常过程:
  • 找到一个合适的异常类
  • 创建这个异常类的一个对象
  • 将对象抛出

4. 创建异常类

在程序中,可能会遇到任何标准程序类都没有能够充分描述清楚的问题,这种情况下,我们需要创建我们自己的异常类。我们需要做的就是定义一个派生类于Exception,或者派生于Exception子类的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器,一个是带有详细描述信息的构造器。

  1. public class FileFormatException extends Exception{
  2.  
  3.  /**
  4.  *
  5.  */
  6.  private static final long serialVersionUID = 1L;
  7.  // 默认构造器
  8.  public FileFormatException(){
  9.  
  10.  }
  11.  // 带有详细描述信息的构造器
  12.  public FileFormatException(String gripe){
  13.  super(gripe);
  14.  }
  15. }
现在我们可以抛出我们自己定义的异常类型了。

  1. throw new FileFormatException;

5. 捕获异常

如果某个异常发生的时候没有任何地方进行捕获,那程序就会终止,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。

  1. package com.qunar.test;
  2. public class ExceptionTest {
  3.  public static void main(String[] args) {
  4.  int a = 10;
  5.  int b = 0;
  6.  System.out.printf("%d / %d = %d",a,b,a/b);
  7.  System.out.println("测试结束...");
  8.  }
  9. }

控制台信息:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.qunar.test.ExceptionTest.main(ExceptionTest.java:8)

从异常信息可以看出程序并没有运行完全,没有输出“测试结束...”,程序就终止,并且在控制台打印出异常信息。

要想捕获异常,必须使用try/catch语句块。

  1. try{
  2.  code
  3.  more code
  4.  more code
  5.  }
  6.  catch (Exception e) {
  7.  handle for this type
  8.  }

(1)如果在try语句块中任何代码抛出一个在catch子句中说明的异常类,那么:
  • 程序将跳过try语句块的剩余代码
  • 程序将执行catch子句的处理器代码
(2)如果在try语句块中代码没有抛出异常,那么程序将跳过catch子句。
(3)如果方法中的任何代码抛出了一个在catch子句没有声明的异常类型,那么这个方法就会立刻退出。

  1. package com.qunar.test;
  2.  
  3. public class ExceptionTest {
  4.  
  5.  public static void main(String[] args) {
  6.  int a = 10;
  7.  int b = 0;
  8.  try{
  9.  System.out.printf("%d / %d = %d",a,b,a/b);
  10.  }
  11.  catch (ArithmeticException e) {
  12.  System.out.println("a / b b 不能等于0");
  13.  }
  14.  System.out.println("测试结束...");
  15.  }
  16. }

运行结果:

a / b  b 不能等于0
测试结束...  

看一个例子:(读取文本程序代码)

  1. public void read(String name){
  2.  try{
  3.  InputStream inputStream = new FileInputStream(name);
  4.  int b;
  5.  while((b = inputStream.read()) != -1){
  6.  // ...
  7.  }//while
  8.  }
  9.  catch (IOException e) {
  10.  e.printStackTrace();
  11.  }
  12.  }

对于一个普通的程序来说,这样的处理异常基本上合乎情理,但是,通常最好的情况是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去处理,如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。

  1. public void read(String name) throws IOException{
  2.  InputStream inputStream = new FileInputStream(name);
  3.  int b;
  4.  while((b = inputStream.read()) != -1){
  5.  // ...
  6.  }//while
  7.  }

如果调用了一个抛出已检查异常的方法,就必须对它进行处理或者将它继续进行传递。

出现了两种处理方式,那到底哪种方式更好呢?
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。如果想传递一个异常,就必须在方法添加一个throws说明符。仔细阅读Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是加到throws列表中。

6. 捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。


  1. try{
  2.  
  3.  }
  4.  catch (FileNotFoundException e) {
  5.  // emergency action for missing files
  6.  }
  7.  catch (UnknownException e) {
  8.  // emergency action for unknown hosts
  9.  }
  10.  catch (IOException e) {
  11.  // emergency action for all other I/O problems
  12.  }










阅读(1934) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~