Java异常

Java异常机制用到的几个关键字:try、catch、finally、throw、throws。
   • try        -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
   • catch   -- 用于捕获异常。catch用来捕获try语句块中发生的异常。
   • finally  -- finally语句块总是会被执行。主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
   • throw   -- 用于抛出异常。
   • throws -- 用在方法签名中,用于声明该方法可能抛出的异常。

   

1. Throwable
  Throwable是 Java 语言中所有错误或异常的超类
  Throwable包含两个子类: Error 和 Exception。通常用于指示发生了异常情况
  Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息

2. Exception
  Exception及其子类是 Throwable 的一种形式,指出了合理的应用程序想要捕获的条件

3. RuntimeException 
  RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
  编译器不会检查RuntimeException异常。例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try...catch...处理该异常",也能通过编译。所说的"编译器不会检查RuntimeException异常"!
  如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

4. Error
  和Exception一样,Error也是Throwable的子类。用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。
  和RuntimeException一样,编译器也不会检查Error。

Java将可抛出(Throwable)的结构分为三种类型:被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。

(01) 运行时异常
定义: RuntimeException及其子类都被称为运行时异常。
特点Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。

        例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
  虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
  如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

(02) 被检查的异常
定义: Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。

     例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。被检查异常通常都是可以恢复的。

(03) 错误
定义: Error类及其子类。
特点: 和运行时异常一样,编译器也不会对错误进行检查。

当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
按照Java惯例,我们是不应该是实现任何新的Error子类的!

 

抛出的异常要适合于相应的抽象

       如果一个方法抛出的异常与它执行的任务没有明显的关联关系,这种情形会让人不知所措。当一个方法传递一个由低层抽象抛出的异常时,往往会发生这种情况。这种情况发生时,不仅让人困惑,而且也"污染"了高层API。
  为了避免这个问题,高层实现应该捕获低层的异常,同时抛出一个可以按照高层抽象进行介绍的异常。这种做法被称为"异常转译(exception translation)"。

  例如,在Java的集合框架AbstractSequentialList的get()方法如下(基于JDK1.7.0_40):

public E get(int index) {
    try {
        return listIterator(index).next();
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
}

       listIterator(index)会返回ListIterator对象,调用该对象的next()方法可能会抛出NoSuchElementException异常。而在get()方法中,抛出NoSuchElementException异常会让人感到困惑。所以,get()对NoSuchElementException进行了捕获,并抛出了IndexOutOfBoundsException异常。即,相当于将NoSuchElementException转译成了IndexOutOfBoundsException异常。

      下面的三个程序每一个都会打印些什么? 不要假设它们都可以通过编译。

    

import java.io.IOException;

public class Arcane1 {

    public static void main(String[] args) {
        try {
            System.out.println("Hello world");
        } catch(IOException e) {
            System.out.println("I've never seen println fail!");
        }
    }
}


public class Arcane2 {
    public static void main(String[] args) {
        try {
            // If you have nothing nice to say, say nothing
        } catch(Exception e) {
            System.out.println("This can't happen");
        }
    }
}


interface Type1 {
    void f() throws CloneNotSupportedException;
}

interface Type2 {
    void f() throws InterruptedException;
}

interface Type3 extends Type1, Type2 {
}

public class Arcane3 implements Type3 {
    public void f() {
        System.out.println("Hello world");
    }
    public static void main(String[] args) {
        Type3 t3 = new Arcane3();
        t3.f();
    }
}

运行结果
(01) 第一个程序编译出错!

Arcane1.java:9: exception java.io.IOException is never thrown in body of corresponding try statement
        } catch(IOException e) {
          ^
1 error

(02) 第二个程序能正常编译和运行。
(03) 第三个程序能正常编译和运行。输出结果是: Hello world

结果说明
(01) Arcane1展示了被检查异常的一个基本原则。它看起来应该是可以编译的:try 子句执行 I/O,并且 catch 子句捕获 IOException 异常。但是这个程序不能编译,因为 println 方法没有声明会抛出任何被检查异常,而IOException 却正是一个被检查异常。语言规范中描述道:如果一个 catch 子句要捕获一个类型为 E 的被检查异常, 而其相对应的 try 子句不能抛出 E 的某种子类型的异常,那么这就是一个编译期错误。

 

02) 基于同样的理由,第二个程序,Arcane2,看起来应该是不可以编译的,但是它却可以。它之所以可以编译,是因为它唯一的 catch 子句检查了 Exception。尽管在这一点上十分含混不清,但是捕获 Exception 或 Throwble 的 catch 子句是合法的,不管与其相对应的 try 子句的内容为何。尽管 Arcane2 是一个合法的程序,但是 catch 子句的内容永远的不会被执行,这个程序什么都不会打印。

(03) 第三个程序,Arcane3,方法 f 在 Type1 接口中声明要抛出被检查异常 CloneNotSupportedException,并且在 Type2 接口中声明要抛出被检查异常 InterruptedException。Type3 接口继承了 Type1 和 Type2,因此, 看起来在静态类型为 Type3 的对象上调用方法 f 时, 有潜在可能会抛出这些异常。一个方法必须要么捕获其方法体可以抛出的所有被检查异常, 要么声明它将抛出这些异常。Arcane3 的 main 方法在静态类型为 Type3 的对象上调用了方法 f,但它对 CloneNotSupportedException 和 InterruptedExceptioin 并没有作这些处理。那么,为什么这个程序可以编译呢? 
  上述分析的缺陷在于对“Type3.f 可以抛出在 Type1.f 上声明的异常和在 Type2.f 上声明的异常”所做的假设。这并不正确,因为每一个接口都限制了方法 f 可以抛出的被检查异常集合。一个方法可以抛出的被检查异常集合是它所适用的所有类型声明要抛出的被检查异常集合的交集,而不是合集。因此,静态类型为 Type3 的对象上的 f 方法根本就不能抛出任何被检查异常。因此,Arcane3可以毫无错误地通过编译,并且打印 Hello world。

     

参考:

http://www.cnblogs.com/skywang12345/p/3544168.html

posted on 2018-09-24 20:48  溪水静幽  阅读(144)  评论(0)    收藏  举报