`
hyshucom
  • 浏览: 809638 次
文章分类
社区版块
存档分类
最新评论

Java中如何正确处理异常

 
阅读更多

1 引言
JAVA语言出现以前,传统的异常处理方式多采用返回值来标识程序出现的异常情况,这种方式虽然为程序员所熟悉,但却有多个坏处。

首先,一个API可以返回任意的返回值,而这些返回值本身并不能解释该返回值是否代表一个异常情况发生了和该异常的具体情况,需要调用API的程序自己判断并解释返回值的含义。

其次,并没有一种机制来保证异常情况一定会得到处理,调用程序可以简单的忽略该返回值,需要调用API的程序员记住去检测返回值并处理异常情况。这种方式还让程序代码变得晦涩冗长,当进行IO操作等容易出现异常情况的处理时,你会发现代码的很大部分用于处理异常情况的switch分支,程序代码的可读性变得很差。
上面提到的问题,JAVA的异常处理机制提供了很好的解决方案。通过抛出JDK预定义或者自定义的异常,能够表明程序中出现了什么样的异常情况;而且JAVA的语言机制保证了异常一定会得到恰当的处理;合理的使用异常处理机制,会让程序代码清晰易懂。
2 JAVA
异常的处理机制
当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroupuncaughtException方法被调用后,遇到异常的当前线程被中止。
3 JAVA
异常的类层次
JAVA
异常的类层次如下图所示

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" type="#_x0000_t75" style="width: 415.5pt; height: 320.25pt"><imagedata o:title="untitled" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif"></imagedata></shape>

1 JAVA异常的类层次
Throwable
是所有异常的基类,程序中一般不会直接抛出Throwable对象,ExceptionErrorThrowable的子类,Exception下面又有RuntimeException和一般的Exception两类。可以把JAVA异常分为三类:
第一类是ErrorError表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA 虚拟机出现错误。Error是一种unchecked Exception,编译器不会检查Error是否被处理,在程序中不用捕获Error类型的异常;一般情况下,在程序中也不应该抛出Error类型的异常。
第二类是RuntimeException, RuntimeException 是一种unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException
第三类是一般的checked Exception,这也是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是checked Exception,如图1中的IOExceptionClassNotFoundExceptionJAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。checked Exception用于以下的语义环境:

1 该异常发生后是可以被恢复的,如一个Internet连接发生异常被中止后,可以重新连接再进行后续操作。
2 程序依赖于不可靠的外部条件,该依赖条件可能出错,如系统IO
3 该异常发生后并不会导致程序处理错误,进行一些处理后可以继续后续操作。

4 JAVA异常处理中的注意事项
合理使用JAVA异常机制可以使程序健壮而清晰,但不幸的是,JAVA异常处理机制常常被错误的使用,下面就是一些关于Exception的注意事项:

1 不要忽略checked Exception
请看下面的代码:
try
{
method1(); //method1
抛出ExceptionA
}
catch(ExceptionA e)
{
e.printStackTrace();
}
上面的代码似乎没有什么问题,捕获异常后将异常打印,然后继续执行。事实上在catch块中对发生的异常情况并没有作任何处理(打印异常不能是算是处理异常,因为在程序交付运行后调试信息就没有什么用处了)。这样程序虽然能够继续执行,但是由于这里的操作已经发生异常,将会导致以后的操作并不能按照预期的情况发展下去,可能导致两个结果:
一是由于这里的异常导致在程序中别的地方抛出一个异常,这种情况会使程序员在调试时感到迷惑,因为新的异常抛出的地方并不是程序真正发生问题的地方,也不是发生问题的真正原因;
另外一个是程序继续运行,并得出一个错误的输出结果,这种问题更加难以捕捉,因为很可能把它当成一个正确的输出。
那么应该如何处理呢,这里有四个选择:

1 处理异常,进行修复以让程序继续执行。
2 重新抛出异常,在对异常进行分析后发现这里不能处理它,那么重新抛出异常,让调用者处理。
3 将异常转换为用户可以理解的自定义异常再抛出,这时应该注意不要丢失原始异常信息(见5)。
4 不要捕获异常。

因此,当捕获一个unchecked Exception的时候,必须对异常进行处理;如果认为不必要在这里作处理,就不要捕获该异常,在方法体中声明方法抛出异常,由上层调用者来处理该异常。

2 不要一次捕获所有的异常
请看下面的代码:
try
{
method1(); //method1
抛出ExceptionA
method2(); //method1
抛出ExceptionB
method3(); //method1
抛出ExceptionC
}
catch(Exception e)
{
……
}
这是一个很诱人的方案,代码中使用一个catch子句捕获了所有异常,看上去完美而且简洁,事实上很多代码也是这样写的。但这里有两个潜在的缺陷,一是针对try块中抛出的每种Exception,很可能需要不同的处理和恢复措施,而由于这里只有一个catch块,分别处理就不能实现。二是try块中还可能抛出RuntimeException,代码中捕获了所有可能抛出的RuntimeException而没有作任何处理,掩盖了编程的错误,会导致程序难以调试。
下面是改正后的正确代码:
try
{
method1(); //method1
抛出ExceptionA
method2(); //method1
抛出ExceptionB
method3(); //method1
抛出ExceptionC
}
catch(ExceptionA e)
{
……
}
catch(ExceptionB e)
{
……
}
catch(ExceptionC e)
{
……
}


3
使用finally块释放资源
finally
关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下三种情况下会进入finally块:
1 try块中的代码正常执行完毕。
2 try块中抛出异常。
3 try块中执行returnbreakcontinue
因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些
代码放入finally块中。当你的程序中使用了外界资源,如数据库连接,文件等,必须将释放这些资源的代码写入finally块中。
必须注意的是,在finally块中不能抛出异常。JAVA异常处理机制保证无论在任何情况下必须先执行finally块然后在离开try块,因此在try块中发生异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。
请看下面的代码:

Connection con = null;
try
{
con = dataSource.getConnection();
……
}
catch(SQLException e)
{
……
throw e;//
进行一些处理后再将数据库异常抛出给调用者处理
}
finally
{
try
{
con.close();
}
catch(SQLException e)
{
e.printStackTrace();
……
}
}
运行程序后,调用者得到的信息如下
java.lang.NullPointerException
at myPackage.MyClass.method1(methodl.java:266)
而不是我们期望得到的数据库异常。这是因为这里的connull的关系,在finally语句中抛出了NullPointerException,finally块中增加对con是否为null的判断可以避免产生这种情况。

4 异常不能影响对象的状态
异常产生后不能影响对象的状态,这是异常处理中的一条重要规则。 在一个函数
中发生异常后,对象的状态应该和调用这个函数之前保持一致,以确保对象处于正确的状态中。
如果对象是不可变对象(不可变对象指调用构造函数创建后就不能改变的对象,即
创建后没有任何方法可以改变对象的状态),那么异常发生后对象状态肯定不会改变。如果是可变对象,必须在编程中注意保证异常不会影响对象状态。有三个方法可以达到这个目的:
1 将可能产生异常的代码和改变对象状态的代码分开,先执行可能产生异常的代码,如果产生异常,就不执行改变对象状态的代码。
2 对不容易分离产生异常代码和改变对象状态代码的方法,定义一个recover方法,在异常产生后调用recover方法修复被改变的类变量,恢复方法调用前的类状态。
3 在方法中使用对象的拷贝,这样当异常发生后,被影响的只是拷贝,对象本身不会受到影响。

5 丢失的异常
请看下面的代码:
public void method2()
{
try
{
……
method1(); //method1
进行了数据库操作
}
catch(SQLException e)
{
……
throw new MyException(“
发生了数据库异常:”+e.getMessage);
}
}
public void method3()
{
try
{
method2();
}
catch(MyException e)
{
e.printStackTrace();
……
}
}
上面method2的代码中,try块捕获method1抛出的数据库异常SQLException后,抛出了新的自定义异常MyException。这段代码是否并没有什么问题,但看一下控制台的输出:
MyException:
发生了数据库异常:对象名称 'MyTable' 无效。
at MyClass.method2(MyClass.java:232)
at MyClass.method3(MyClass.java:255)
原始异常SQLException的信息丢失了,这里只能看到method2里面定义的MyException的堆栈情况;而method1中发生的数据库异常的堆栈则看不到,如何排错呢,只有在method1的代码行中一行行去寻找数据库操作语句了,祈祷method1的方法体短一些吧。
JDK
的开发者们也意识到了这个情况,在JDK
<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="font-size: small">1.4.1</span></chsdate>中,Throwable类增加了两个构造方法,public Throwable(Throwable cause)public Throwable(String message,Throwable cause),在构造函数中传入的原始异常堆栈信息将会在printStackTrace方法中打印出来。但对于还在使用JDK1.3的程序员,就只能自己实现打印原始异常堆栈信息的功能了。实现过程也很简单,只需要在自定义的异常类中增加一个原始异常字段,在构造函数中传入原始异常,然后重载printStackTrace方法,首先调用类中保存的原始异常的printStackTrace方法,然后再调用super.printStackTrace方法就可以打印出原始异常信息了。可以这样定义前面代码中出现的MyException类:
public class MyExceptionextends Exception
{
//
构造函数
public SMException(Throwable cause)
{
this.cause_ = cause;
}

public MyException(String s,Throwable cause)
{
super(s);
this.cause_ = cause;
}
//
重载printStackTrace方法,打印出原始异常堆栈信息
public void printStackTrace()
{
if (cause_ != null)
{
cause_.printStackTrace();
}
super.printStackTrace(s);
}

public void printStackTrace(PrintStream s)
{
if (cause_ != null)
{
cause_.printStackTrace(s);
}
super.printStackTrace(s);
}

public void printStackTrace(PrintWriter s)
{
if (cause_ != null)
{
cause_.printStackTrace(s);
}
super.printStackTrace(s);
}
//
原始异常
private Throwable cause_;
}

6 不要使用同时使用异常机制和返回值来进行异常处理
下面是我们项目中的一段代码
try
{
doSomething();
}
catch(MyException e)
{
if(e.getErrcode == -1)
{
……
}
if(e.getErrcode == -2)
{
……
}
……
}
假如在过一段时间后来看这段代码,你能弄明白是什么意思吗?混合使用JAVA异常处理机制和返回值使程序的异常处理部分变得丑陋不堪,并难以理解。如果有多种不同的异常情况,就定义多种不同的异常,而不要像上面代码那样综合使用Exception和返回值。
修改后的正确代码如下:
try
{
doSomething(); //
抛出MyExceptionAMyExceptionB
}
catch(MyExceptionA e)
{
……
}
catch(MyExceptionB e)
{
……
}


7
不要让try块过于庞大
出于省事的目的,很多人习惯于用一个庞大的try块包含所有可能产生异常的代码,
这样有两个坏处:
阅读代码的时候,在try块冗长的代码中,不容易知道到底是哪些代码会抛出哪些异常,不利于代码维护。
使用try捕获异常是以程序执行效率为代价的,将不需要捕获异常的代码包含在try块中,影响了代码执行的效率。

参考资料
[1] Joshua Bloch Effective Java Programming Language Guide
[2]
http://java.sun.com/

http://blog.csdn.net/flyingBox/archive/2008/03/20/2201213.aspx

分享到:
评论

相关推荐

    Java中正确的异常处理.pdf

    Java中正确的异常处理.pdf

    JAVA的异常处理机制

    JAVA的异常处理机制 java学习,值得学习

    浅析JAVA异常处理机制.pdf

    异常处理是Java语言的重要机制,正确、合理地处理异常对系统的健壮性和稳定性提供了强有力的支持。异常的处理主要包括捕捉异常、程序流程的跳转和异常处理语句块的定义等。

    java试验报告之异常处理程序设计

    2.掌握异常处理程序设计技术并能正确应用。 二、实验内容 算术异常ArithmeticException(数组越界ArrayIndexOutOfBoundsException、字符串越界异常StringIndexOutOfBoundsException等)处理程序设计与调试。 三、...

    Java异常处理.md

    在Java中,通过try、catch和finally语句来实现异常捕获与处理: 1. **基础异常捕获**: - 当代码执行过程中出现如除数为零的`ArithmeticException`等错误时,Java会立即转至相应的catch块进行异常处理,如输出错误...

    Java编程中异常处理的优劣之道

    Java编程中异常处理的优劣之道 ...不过,我认为很多人其实并没有真正掌握正确处理异常情况的方法和策略,最多也就不过了解个大概,知道点概念。本文就对三种不同程度和质量的Java异常处理进行了讨论

    Java异常处理机制的静态编译实现与优化

    异常处理机制通常由编译器和异常处理机制的运行时支持函数共同实现,因此,如何正确高效地实现异常处理机制是设计编译器和异常处理运行时支持函数所要关心的重要问题。 Java程序的编译运行有两种方式:在JVM上动态编译...

    Java异常处理在生活中的实际应用:电子邮箱注册验证.txt

    这个示例代码展示了一个生活中实际应用场景,即电子邮箱注册验证。我们定义了一个自定义异常类...它模拟了生活中的实际应用场景,提供了一种处理异常情况的方法,以保证系统的稳定性和用户体验。

    JAVA错误处理大集合

    JAVA错误处理大集合 异常 JAVA错误处理大集合 JAVA错误处理大集合

    Java高级程序设计(第二版)--第2章-异常处理.pptx

    第2章 异常处理 2.1 什么是异常 2.2 处理异常 2.3 自定义异常与throw关键字 2.4 throws关键字 2.5 Java的内置异常 Java高级程序设计(第二版)--第2章-异常处理全文共19页,当前为第2页。 本章目标 理解异常及其作用 ...

    java的异常与流

    使用异常处理机制和输入/输出处理机制编写一个程序, * 实现当用户输入一个文件名时,判断这个文件名是否存在, 若不存在,允许用户重新输入,直到输入了一个正确的文件名后, 则打开这个文件并将文件中的内容...

    详解Java异常处理中throw与throws关键字的用法区别

    主要介绍了详解Java异常处理中throw与throws关键字的用法区别,这也是Java面试题目中的常客,需要的朋友可以参考下

    Java中断异常的正确处理方法

    主要给大家介绍了关于Java中断异常的正确处理方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    【Java】异常类体系及异常处理

    如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失。 如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法...

    一些Java面试题库

    共四十道题,由易到难 1. Java的接口和C++的虚类的相同和不同处 2. Java中的异常处理机制的简单原理和应用 。。。。

    Java初学者很好的参照实例 面向对象编程 异常处理 图形化界面的搭建 多类程序

    4、每个实例中多包含异常处理操作。‘免费提供’由于本人水平有限,程序包含bug在所难免,不过可以保证绝大部分是正确,希望能给初学者解除一些困惑,那就是本人的最终目的。感谢大家支持!!!

    《Java程序设计》课程实验指导书程序代码(答案)(实验五)

    理解 java 语言中独特的异常处理机制; 掌握异常处理方法; 正确地使用捕获异常和声明抛弃异常的两种异常处理的方法; 了解自定义异常类的使用; 理解抛出异常和声明抛出异常的区别与联系; 二、实验内容: 1. 从...

    java计算器的设计

    熟悉java.awt包中的组件,掌握图形界面设计方法,理解委托事件处理模型。 2.题意: 请设计并实现Windows系统中“计算器”的窗口及功能。 3.实验要求: (1)设计图形界面添加菜单:窗口上添加各种组件及菜单,并处理...

    Java开发技术大全(500个源代码).

    示例描述:本章学习Java的异常处理。 demoException_1.java 异常示例1 demoException_2.java 异常示例2 demoException_3.java 异常示例3 demoException_4.java 异常示例4 demoException_5.java 异常示例5 ...

Global site tag (gtag.js) - Google Analytics