try throw java_java的异常处理机制详解

异常机制已经成为判断一门编程语言是否成熟的标准,异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。    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的系统定义的大致的异常类的层次图如下(不全面,比如没有SQLException等):        java中定义的异常(Exception)和错误(Error)都继承自Throwable类。其中错误的产生大多是由于运行环境jvm导致的,这部分错误我们通过程序很难纠正,如果真的出现又必须纠正,那可能就要涉及到jvm调优的问题。如jvm的垃圾回收机制(GC)之类。  * 而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块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。如果有finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用再跳回去执行try块、catch块里的任何代码了。所以,一般情况下,不要在finally块中使用return或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。  以下四种情况将会导致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。

参考文献:

JDK API 1.7 http://blog.sina.com.cn/s/blog_9d88a5770101gsf4.htmlhttp://lavasoft.blog.51cto.com/62575/18920 Thinking in java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值