Java异常处理

简介

异常处理是对可能出现的异常进行处理,以防止程序遇到异常时被卡死,处于一直等待,或死循环。

异常有两个过程,一个是抛出异常;一个是捕捉异常。

  java语言可以说是提供了过于完善的异常处理机制,其包括ErrorException两个部分。他们都继承自共同的基类Throwable

   Error属于JVM运行中发生的一些错误,虽然并不属于开发人员的范畴,但是有些Error还是由代码引起的,比如StackOverflowError经常由递归操作引起,一般无法挽救,只能靠JVM。

   Exception分为检查类型(checked)和未检查类型(unchecked)。检查类型的异常:程序员明确的去声明或者用try..catch语句来处理的异常,而非检查类型的异常则没有这些限制,比如我们常见的 NullPointerException 就是非检查类型的,他继承自RuntimeException。

   java是目前主流编程语言中唯一一个推崇使用检查类型异常的,至少sun是这样的。关于使用checked还是unchecked异常的论战一直很激烈。下面是一张java语言中异常的类关系图。

wKiom1LgzG-zvfltAABTfW8gzT8956.jpg

基本使用

   我们在使用java的文件或者数据库操作时接触的一些异常:比如IOException、SQLException等,这些方法被声明可能会抛出某种异常,我们需要对其进行捕获处理,这就需要基本的try..catch语句了。下图就是我们经常写的一个基本结构。try语句块中写”可能会抛出异常的代码“,catch语句块对其进行捕获。我们看到catch的参数写的是一个Exception对象,这就意味着这个语句块可以捕获所有的检查类型的异常(虽然这并不是一种好的写法),finally总是会保证在最后执行,一般我们在里面处理一些清理的工作,比如关闭文件流或者数据库,网络等操作。

wKiom1LgzLyCQz9KAADGJQrTYVw726.jpg

   当然上面的语句块结构是灵活的,但try是必须有的,catch和finally至少有一个,当然catch的数量可以有多个。有时try语句块中可能抛出多种类型的异常,这个时候,我们可以写多个catch语句来捕获不同类型的异常,一个比较好的写法如下:

try{
// ..invoke some methods that may throw exceptions
}catch(ExceptionType1 e){
//...handle exception
}catch(ExceptionType2 e){
//...handle exception
}catch(Exception e){
//...handle exception
}finally{
//..do some cleaning :close the file db etc.
}

   当异常不满足前两个type的时候,exception会将异常捕获。我们发现这个写法比较类似switch case的结构控制语句,一旦某个catch得到匹配后,其他的就不会就匹配了,有点像加了break的case。有一点需要注意catch(Exception)一定要写在最后面,catch是顺序匹配的,后面匹配Exception的子类,编译器就会报错。

   try语句实际上执行的时候会导致栈操作。即要保存整个方法的调用路径,这势必会使得程序变慢。fillInStackTrace()是Throwable的一个方法,用来执行栈的操作,他是线程同步的,本身也很耗时。这里问题在StackOverFlow上曾经有过一段非常经典的讨论,原文。 的确当我们在try中什么都不做,或者只执行一个类似加法的简单调用,那么其执行效率和goto这样的控制语句是几乎一样的。但是谁会写这样的代码呢?

   总之不要总是试图通过try catch来控制程序的结构,无论从效率还是代码的可读性上都不好。

try catch好的一面

   try catch虽不推荐用于程序结构的控制,但也具有重要的意义,其设计的一个好处就是,开发人员可以把一件事情当做事务来处理,事务也是数据库中重要的概念,举个例子,比如完成订单的这个事务,其中包括了一个动作序列,包括用户提交订单,商品出库,关联等。当这个序列中某一个动作执行失败的时候,数据统一恢复到一个正常的点,这样就不会出现你付完了帐商品却没有给你的情况。我们在try语句块中就像执行一个事务一样,当出现了异常,就会在catch中得到统一的处理,保证数据的完整无损。其实很多不好的代码也是因为没有好好利用catch语句的语言,导致很多异常就被淹没了。

定制详细的异常

   我们可以自己定义异常,以捕获处理某个具体的例子。创建自己的异常类,可以直接继承Exception或者RuntimeException。区别是前者是简称类型的,而后者为检查类型异常。Sun官方力挺传统的观点,他建议开发者都是用检查类型的异常,即你一定要去处理的异常。下面是定义的一个简单的异常类.

public class SimpleException extends Exception{
SimpleException(){}
SimpleException(String info){
super(info);
}
}

   我们覆写了两个构造方法,这是有意义的。通过传递字符串参数,我们创建一个异常对象的时候,可以记录下详细的信息,这样这个异常被捕获的时候就会显示我们之前定义的详细信息。比如用下面的代码测试一下我们定义的异常类:

public class Test {
public void fun() throws SimpleException{
throw new SimpleException("throwing from fun");
}
public static void main(String[] args) {
Test t = new Test();
try{
t.fun();
}catch(SimpleException e){
e.printStackTrace();
}
}
}

   运行就会得到下面的结果 printStackTrace是打印调用栈的方法,他有三个重载方法,默认的是将信息输出到System.err。这样我们就可以清晰的看到方法调用的过程,有点像操作系统中的中断,保护现场。

SimpleException: throwing from fun
at Test.fun(Test.java:4)
at Test.main(Test.java:9)

略微麻烦的语法

   我们自己实现的异常有时候会用到继承这些特性,在异常继承的时候有一些限制。那就是子类不能抛出基类或所实现的接口中没有抛出的异常.比如有如下的接口:

public interface InterfaceA {
public void f() throws IOException;
}

    我们的Test类实现这个接口,那么Test的f方法要么不抛出异常,要么只能抛出IOException,其实关于这里还有更琐碎的规矩,详细可以参考《Java Puzzlers》第37个谜题。所以这和传统的继承和实现接口正好相反,面向对象的继承是扩大化,而这正好是缩小了。


你可能感兴趣的:(exception,异常)