异常机制已经成为判断一门编程语言是否成熟的标准,异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

Java异常的处理主要依赖于try,catch,finally,throws,throw这五个关键字。下面分别介绍它们:
1. try:try块中主要放置可能会产生异常的代码块。如果执行try块里的业务逻辑代码时出现异常,系统会自动生成一个异常对象,该异常对象被提交给运行环境,这个过程被称为抛出(throw)异常。Java环境收到异常对象时,会寻找合适的catch块(在本方法或是调用方法),如果找不到,java运行环境就会终止,java程序将退出
2. catch:catch块中放置当出现相应的异常类型时,程序需要执行的代码。当try中语句可能发生多个异常的时候可以由多个catch。
3. finally:finally中存放一定会执行的代码,异常机制保证finally代码总是会被执行。当遇到try或catch中return或throw之类可以终止当前方法的代码时,jvm会先去执行finally中的语句,当finally中的语句执行完毕后才会返回来执行try/catch中的return,throw语句。如果finally中有return或throw,那么将执行这些语句,不会在执行try/catch中的return或throw语句。finally块中一般写的是关闭资源之类的代码。
4. throws:在方法的签名中,用于抛出次方法中的异常给调用者,调用者可以选择捕获或者抛出,如果所有方法(包括main)都选择抛出。那么最终将会抛给JVM。JVM打印出栈轨迹(异常链)。
5. throw:用于抛出一个具体的异常对象。

Java对异常的处理是按异常分类处理的,不同异常有不同的分类,每种异常都对应一个类型(class),每个异常都对应一个异常(类的)对象。java的系统定义的大致的异常类的层次图如下:

java 如何在try里面抛异常 java里面的try_java

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。

Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception(异常):是程序本身可以处理的异常。
Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

* 而Java的异常分为两种,checked异常(编译时异常)和Runtime异常(运行时异常)*

编译时异常: java认为checked异常都是可以再编译阶段被处理的异常,所以它强制程序处理所有的checked异常,java程序必须显式处理checked异常,如果程序没有处理,则在编译时会发生错误,无法通过编译。

运行时异常: 在编译的过程中,Runtime异常无须处理也可以通过编译。所有的Runtime异常原则上都可以通过纠正代码来避免。

既然说java的异常都是一些异常类的对象,那么这些异常类也有一些方法我们应该了解:

1. getMessage();返回该异常的详细描述字符 
 2. printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。(异常链) 
 3. printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定的输出流 
 4. getStackTrace():返回该异常的跟踪栈信息。

再细讲java是如何处理异常之前我们在来重申一下两个重要问题
1. 为什么要有异常?对于构造大型、健壮、可维护的应用系统而言,错误处理是整个应用需要考虑的重要方面。Java异常处理机制,在程序运行出现意外时,系统会生成一个Exception对象,来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。其中checked异常体现了java设计哲学:没有完善处理的代码根本不会被执行,体现了java的严谨性。
2. 异常有什么用? (1)可以对可能出现的异常进行更清晰的处理和说明,比如在finally中关闭资源或连接,或者在catch块中捕获异常打印信息到屏幕和日志等。(2)应用异常来处理业务逻辑,可以这么做,但是这有违背异常设计的初衷(异常实质上可以是一个if else语句,当然可以用作业务处理)。

明确了上面两个问题之后,我们就来看一下java的具体的异常处理机制。

1.使用try…catch捕获异常:

java提出了一种假设,如果try中的语句一切正常那么将不执行catch语句块,如果try中语句出现异常,则会抛出异常对象,由catch语句块根据自己的类型进行捕获。若没有相应的catch块,则抛出。
所以其执行步骤可以总结为以下两点:
(1) 如果执行try块中的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给java运行环境,这个过程称为抛出(throw)异常。
(2) 当java运行环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的cathc块并把该异常对象交给catch块处理,那这个过程称为捕获(catch)异常;如果java运行时环境找不到捕获异常的catch块,则运行时环境终止,java程序也将退出。
下面还有几点注意事项需要大家注意:
注意一: 不管程序代码块是否处于try块中,甚至包括catch块中代码,只要执行该代码时出现了异常,系统都会自动生成一个异常对象,如果程序没有为这段代码定义任何catch块,java运行环境肯定找不到处理该异常的catch块,程序肯定在此退出。
注意二: 进行异常捕获时,一定要记住先捕获小的异常,再捕获大的异常。
注意三: 看下面一段java程序,我们来说明java对finally的处理方式:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test{
    public static void main(String[] args) {
        FileInputStream fis=null;
        try {
            fis=new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
            // return语句强制方法返回
            return;
            // 使用exit来退出虚拟机
            //System.exit(1);
        }finally{
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                fis=null;
            }
            System.out.println("fis资源已被回收");
        }
    }
}

运行这个程序,在catch中使用return而不exit可以得到如下结果:

a.txt (系统找不到指定的文件。)
fis资源已被回收

如果使用exit而不是return,那么将会得到如下结果:

a.txt (系统找不到指定的文件。)
try-catch-finally代码块的执行顺序:

A)try没有捕获异常时,try代码块中的语句依次被执行,跳过catch。如果存在finally则执行finally代码块,否则执行后续代码。

B)try捕获到异常时,如果没有与之匹配的catch子句,则该异常交给JVM处理。如果存在finally,则其中的代码仍然被执行,但是finally之后的代码不会被执行。

C)try捕获到异常时,如果存在与之匹配的catch,则跳到该catch代码块执行处理。如果存在finally则执行finally代码块,执行完finally代码块之后继续执行后续代码;否则直接执行后续代码。另外注意,try代码块出现异常之后的代码不会被执行。(见下图:)

java 如何在try里面抛异常 java里面的try_java 如何在try里面抛异常_02

总结

try代码块:用于捕获异常。其后可以接零个或者多个catch块。如果没有catch块,后必须跟finally块,来完成资源释放等操作,另外建议不要在finally中使用return,不用尝试通过catch来控制代码流程。

catch代码块:用于捕获异常,并在其中处理异常。

finally代码块:无论是否捕获异常,finally代码总会被执行。如果try代码块或者catch代码块中有return语句时,finally代码块将在方法返回前被执行。注意以下几种情况,finally代码块不会被执行:

1、 在前边的代码中使用System.exit()退出应用。

2、 程序所在的线程死亡或者cpu关闭

3、 如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束,同时该异常会覆盖前边抛出的异常。

以下四种情况将会导致finally块不执行:

(1)在finally语句块中发生了异常

(2)在前面的代码中使用了System.exit()退出虚拟机

(3)程序所在线程死亡

(4)关闭cpu

  • ps:在论坛上看到有朋友问:如果catch捕获了异常,那么try…catch语句块之后的语句是否会执行?
  • 答案是如果catch块或finally块中没有throw语句或者return语句,那么try…catch之后的语句就一定会执行。因为异常已经被捕获和处理了呀~为什么后面的语句为什么不能执行呢。
  • ps:可能又会有朋友问,如果try…catch块之后的语句中有使用到try中的引用,而try中的语句失败了,后面的怎么执行?
  • 放心,如果真的有这种情况,那java一定会要求你讲这些语句和与那些可能失败的语句一起放入try…catch块中的。

2.使用throw(s)处理异常:

如果当前出现的异常在本方法中无法处理,我们只能抛出异常。
如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理(异常没有在本地处理,逻辑上throw之后的程序不会在进行)。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块(这属于异常没有得到处理,将终止出现异常的线程),将按照下面的步骤处理:
第一、调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
第二、如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
关于throw的用法我们有几点注意事项要注意:
注意一: throw语句后不允许有紧跟其他语句,因为这些没有机会执行(块外也不行,因为不会执行,无论是否被调用方捕获。如果异常是在本方法内部throw直接捕获,那是可以执行块后面的代码的,记住只要throw论文,throw之后的代码都不会在执行)。我以一段程序来说明这个问题:

public class TestException {  
    public static void exc() throws ArithmeticException{
        int a =1;
        int b=4;
        for (int i=-2;i<3;i++){
                    a=4/i;
                System.out.println("i="+i);
        }
    }
    public static void caexc(){
        try {
            exc();
        } catch (ArithmeticException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {  
        TestException.caexc();      
    }  
}

输出结果为:

i=-2
i=-1
java.lang.ArithmeticException: / by zero
    at TestException.exc(TestException.java:8)
    at TestException.caexc(TestException.java:14)
    at TestException.main(TestException.java:21)
  • 虽然捕获了异常,但由于原来的线程已经throw,所以后面的代码均不会得到执行。
    注意二: 如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。
    注意三: throw和throws关键字的区别:
    throw用来抛出一个异常,在方法体内。语法格式为:throw 异常对象。
    throws用来声明方法可能会抛出什么异常,在方法名后,语法格式为:throws 异常类型1,异常类型2…异常类型n
    注意四: throw语句抛出异常的两种情况:
    1.当throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把异常交给方法的调用者处理。
    2.当throw语句抛出的异常是Runtime异常,则该语句无须放在try块内,也无须放在带throws声明抛出的方法中,程序既可以显式使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法的调用者处理。
    Runtime异常和Checked异常在抛出时的区别见下面这段代码:
public class TestException {  
    public static void throw_checked(int a) throws Exception{
        //Exception默认为checkedExcption
        if(a>0) throw new Exception("Exception:a>0");
    }
    public static void throw_runtime(int a) {
        if(a>0) throw new RuntimeException("runtimeException:a>0");
    }
    public static void main(String[] args) {  
        int a=1;
        try {
            throw_checked(a);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        throw_runtime(a);
    }  
}

可见Runtime异常的灵活性比Checked的灵活性更强。因为Checked异常必须要被显式捕获或者显式抛出,所以Runtime写的更方便,我们自定义异常一般都是用Runtime异常。

3.自定义异常:

用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。异常类通常需要提供两种构造器:一个是无参数的构造器,另一个是带一个字符串的构造器,这个字符串将作为该异常对象的详细说明(也就是异常对象的getMessage方法的返回值)。下面给出一段自定义异常MyException的代码:

public class MyException extends RuntimeException{
    public MyException(){
    }
    public MyException(String s){
        super(s);
    }
}

用throws声明方法可能抛出自定义的异常,并用throw语句在适当的地方抛出自定义的异常。捕获自定义异常的方法与捕获系统异常一致。还可以异常转型。

4.异常链(异常跟踪栈):

异常对象的printStackTrace方法用于打印异常的跟踪栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。虽然printStackTrace()方法可以很方便地追踪异常的发生状况,可以用它来调试,但是在最后发布的程序中,应该避免使用它。而应该对捕获的异常进行适当的处理,而不是简单的将信息打印出来。


5.总结:

(1) catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么转译,要么重新抛出新类型的异常。
(2) 不要用try…catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。
(3) 避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。
(4) 细化异常的类型,不要不管什么类型的异常都写成Excetpion。

参考文献:

  1. JDK API 1.7
  2. http://blog.sina.com.cn/s/blog_9d88a5770101gsf4.html

http://lavasoft.blog.51cto.com/62575/18920

  1. Thinking in java