`
coach
  • 浏览: 390231 次
  • 性别: Icon_minigender_2
  • 来自: 印度
社区版块
存档分类
最新评论

彻底攻克java异常

阅读更多
1、JAVA异常的处理机制
    当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。

2、JAVA异常的类层次



图1 JAVA异常的类层次


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


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

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

因此,当捕获一个checked 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块中执行return、break、continue。
因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些
代码放入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)
而不是我们期望得到的数据库异常。这是因为这里的con是null的关系,在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的开发者们也意识到了这个情况,在JDK1.4.1中,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();   //抛出MyExceptionA和MyExceptionB
}
catch(MyExceptionA e)
{
……
}
catch(MyExceptionB e)
{
     ……
}

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

相关推荐

    java超详细思维导图XMIND格式

    通过这个思维导图,学习者可以按照自己的进度和兴趣逐个攻克Java的各个知识点,从基础到高级,从理论到实践,逐步建立完整的知识体系。对于初学者,它能帮助快速入门;对于有经验的开发者,它能作为一个很好的复习和...

    铁道部借助英特尔技术攻克多项远程管理挑战

    兰州铁路局和上海铁路局采用英特尔:registered: 主动管理技术(英特尔:registered: AMT)的电脑用于远程电脑管理,并进一步将英特尔:registered: 主动管理技术集成到了自己的电子文件交付系统(Electronic Document ...

    java编程案例--10道精选的JAVA小题

    Java编程是一种广泛使用的...通过这10道精选的JAVA小题,我们可以逐一攻克Java编程的基石,加深对语言特性的理解,并逐步提高编程能力。实践中遇到的问题和解决方案将成为宝贵的经验,为日后的项目开发打下坚实基础。

    c++虚函数表解析(彻底攻克继承和虚函数)

    彻底搞清楚继承是个什么东西 彻底搞清楚虚函数和虚函数表是个什么东西

    c语言:我眼中的指针(教你彻底认识指针)

    ### C语言:彻底理解指针 #### 一、引言 在C语言中,指针是一种非常重要的数据类型,它能够存储内存地址,是进行高效内存管理的关键工具。本文将从多个角度深入探讨指针的概念及其应用,帮助读者建立起对指针的...

    JAVA笔记(根据马士兵的java视频整理).pdf

    这些知识点构成了JAVA编程的基石,也是学习者必须首先攻克的难关。 在马士兵的JAVA视频教程中,他不仅详细讲解了JAVA的基础知识,比如变量、循环、分支结构、数组、常用类等,还对I/O Stream、Collection/Generic、...

    JAVA开发经理_java个人简历模板.doc

    JAVA 开发经理个人简历模板 根据提供的简历模板,我们可以总结出以下知识点: Java EE 和 Java ME * Java EE(Enterprise Edition):用于开发大型企业级应用程序,涉及技术包括 SSH(Spring + Struts + ...

    JAVA200道经典例题

    3. **异常处理**:JAVA中的try-catch-finally语句块用于捕获和处理运行时错误,理解异常类的层次结构和如何自定义异常至关重要。 4. **数组和集合**:数组是存储固定大小元素的容器,而集合(如ArrayList、...

    java面试百科全书

    从基础知识的类、对象、封装、继承和多态,到异常处理、集合框架、IO流、多线程和网络编程,它覆盖了Java语言的主要组成部分。这些知识点是每位求职者都需要掌握的,因为它们构成了考察求职者是否具备编程基本素养的...

    Java 最新面试宝典 java 面试宝典 java 最新面试宝典 Java面试Java 工程师进阶知识完全扫盲

    面试技术攻克篇 Java基础知识 Java Web 数据库原理 设计模式 数据结构与算法 海量数据处理 SSM企业级应用(Spring+SpringMVC+Mybatis) SpringBoot+SpringCloud 需要完整版Java面试宝典的朋友,只需要关注...

    java程序设计实用教程(第3版)习题解答

    通过《Java程序设计实用教程(第3版)》的习题解答,你可以逐一攻克这些知识点,加深对Java的理解,提升编程实战能力。在实践中,你可以使用IDE如MyEclipse来导入并运行这些习题,这样不仅能够检验答案的正确性,还能...

    java concurrent source code

    很多人学习完JavaSE/JavaEE之后想往更深入的技术进行探索,比如对大数据、分布式、高并发类的专题进行攻克时,立即遇到针对java.lang包中线程类的学习,但线程类的学习并不像JDBC一样简单,学习曲线陡峭,多弯路与...

    攻克java大数类

    我不想学习了 不,我想!! 这篇博客作为我学习大数类的笔记。(我会持续更新的) BigInteger操作大整数 BigDecimal指定小数的保留位数 首先,long的范围是:-2^63 ~ 2^63-1 超过这个范围,就是用大数类,最好用...

    攻克Data动态获取网页评论,保存数据库

    【攻克Data动态获取网页评论,保存数据库】是一个关于利用特定工具——攻克Data,来抓取网页上的评论数据并存储到数据库的过程。这个过程涉及到网络爬虫技术、JSON解析以及数据库管理等多个IT领域的知识点。 1. **...

    JAVA资料8-10学习

    多线程和并发编程是JAVA的高级主题之一,也是学习者在8-10阶段需要重点攻克的内容。在JAVA中,线程可以用来并发执行任务,提高程序效率和用户体验。学习者将学习如何创建线程、如何使用同步机制控制线程间的资源访问...

    上机题java篇(全).rar!!!!

    Java编程语言是面向对象的、跨平台的编程语言,由Sun Microsystems(现为Oracle Corporation的一部分)于1995年...建议按照题目分类,逐个攻克,同时结合教材和在线资源深入学习,以达到全面提高Java编程技能的目标。

    200个Java初学者必看的小程序

    通过"200个Java初学者必看的小程序",初学者可以逐一攻克这些知识点,每完成一个小程序就是一个小成就,积累起来就是扎实的编程基础。这些小程序不仅提供了理论知识的验证,还锻炼了实际编程技能,为以后的项目开发...

    java员工转正述职报告.pdf

    - 工作表现:详细描述在试用期间完成的工作任务、项目,包括解决过的问题、技术难点的攻克以及个人贡献。 - 学习成长:在试用期间所学到的新知识、技术、工具,以及如何快速融入团队和公司文化。 - 问题与挑战:在...

    大学生攻克Linux系统教程

    【大学生攻克Linux系统教程】 本教程专为对Linux操作系统感兴趣的初学者设计,旨在提供一个从零开始学习Linux的全面指南。教程内容涵盖了Linux系统的安装、基本操作、文本编辑器VI的使用、调试工具GDB的基础知识,...

    java2(第三版)书中例题及实验答案

    在学习过程中,读者应结合书中的例题和实验答案,逐一攻克难点,逐步提升编程技能。同时,不断实践是掌握Java编程的关键,只有将理论知识付诸实践,才能真正理解和掌握Java的魅力。对于初学者来说,这本书的课后答案...

Global site tag (gtag.js) - Google Analytics