Java中异常处理方法总结

异常(Exception):指程序运行过程中出现的非正常现象。

1、  Java异常的异常处理机制

早期的情况:

早期使用的程序设计语言是没有提供专门进行异常处理功能的,程序设计人员只能苦逼的使用条件语句对各种可能设想到的错误情况进行判断,来捕捉特定的异常,然后进行相应的处理。这样的处理方式,往往要整出大段大段的if…else语句。本来需要完成相应功能的代码块很小,但是加上这样针对异常处理的条件语句使得代码显得非常臃肿,这样一来代码的可读性和可维护性就下降了,而且有时候还会遗漏意想不到的异常情况。

Java的出现:

针对上面的情况Java提供了强大的异常处理机制,可以说为程序猿带来了福音,Java的异常处理机制可以很方便的在程序中监视可能发生异常的程序块,并将所有的异常处理代码集中放在程序的某处,使完成正常功能的程序代码与进行异常处理的程序代码分开。通过异常处理机制,减少了编程人员的工作量,增强了异常处理的灵活性,并使得程序的可读性和可维护性大大的提高了。

在Java的处理机制中,引入了一些用来描述和处理异常的类,每个异常类反映一类运行的错误,在类的定义中包含了该类异常的信息和对异常进行处理的方法。对程序运行的过程中发生某一个异常现象时,系统就产生了一个与之相应的异常类对象,并交由系统中的相应机制进行处理,以避免系统崩溃或其他对系统有害的结果发生,保证了程序运行的安全性。

2、  Java异常类的定义

Java中,把异常分为:错误(Error)和异常情况(Exception)。

错误(Error):指程序本身存在非法的情形,这些情形常常是因为代码存在着问题而引起的。而且编程人员可以通过对程序进行更新仔细的检查,把这种错误的情形减小到最小,从理论上讲,这些情形是可以避免的。

异常情况(Exception):表示另外一种“非同寻常”的错误。这种错误通常是不可预测的。常见的异常情况包括内存不足,找不到所需要的文件等。

Throwable类派生了两个子类:Exception和Error。

Error类描述内部的错误,它由系统保留,程序不能抛出这个类型的对象,Error类的对象不可捕获,不可以恢复,出错时系统通知用户并终止程序;

Exception类则供应程序的使用,所有的Java异常类都是系统类库中的Exception类的子类。

 

继承关系:

 

 

Throwable类是所有异常类的父类,实现了Serializable接口。Throwable有两个很重要的子类:Exception和Error,分别表示异常和错误。异常一般表示可检测、可恢复或者在编码中可以避免的问题,一般是比较轻度的错误;错误通常表示严重的问题,大多数情况下与代码的执行无关,比如OutOfMemoryError。

其中受检的异常表示期望调用者在异常发生时能够采取一些恢复的措施,或者将该异常传播出去。比如我们的TaoShareException,就属于受检的异常。当一个方法抛出受检异常时,意思就是告诉调用者,要调用我这个方法,你必须捕获我抛出的异常。当然调用者捕获这个异常之后可以忽略,但这通常不是个好办法,最起码应该打印一条日志。

运行时异常和错误属于未受检的可抛出结构,在行为上是相同的:他们都不需要被捕获。这是因为当我们的程序抛出这两种结构时,通常代表的是该错误不可被恢复,继续执行下去有害无益,比如IndexOutOfBoundsException、ArithmeticException和OutOfMemoryError。IndexOutOfBoundsException表示数组或者字符串超出了索引范围,ArithmeticException表示运算异常(比如分母为0),OutOfMemoryError表示JVM没有足够的内存来分配。前两种异常都属于RuntimeException,表明这种异常属于编程错误,我们应该在我们的程序中就避免这种问题,而不是通过捕获来解决。后一种属于错误,是我们无法通过编码来解决的,这种错误通常都是比较严重的问题,一般是JVM出了问题。

2 Java异常处理原则

2.1 不要忽略异常

这是处理异常最基本的原则,当一个异常被抛出的时候,说明程序中某个地方出了问题,忽略异常可能达不到我们期望的效果。我们应该在程序中对出现的异常进行处理,或者是继续抛出。至少,我们应该包含一条语句,将该异常记录进日志,便于我们将来的分析。

2.2 尽量少用异常,优先使用标准异常

异常的处理会有一定的开销,除非是在出现异常的情况下,否则不要将异常用于普通的控制流。比如,不要将异常用来检测数组是否越界。如下:


//摘自《Effective Java》

try{

int i=0;

while(true)

range[i++].climb();

}catch(ArrayIndexOutOfBoundsException e){

}

还有,不要总是依赖异常来解决空指针问题,我们在调用方法之前就应该判断对象是否为null。注意要判断所有需要的对象是否为null,我以前碰到一个问题就是只判断了部分为null,如下代码所示:


If(baskItem.getStatus() != null && baskItem.getStatus().getStatus() != Status.TOP.getStatus())

结果在运行的过程中,却出现了baskItem为null的情况。当然这种情况是很少遇到的,但是在某些极端的情况下还是会遇到。

在使用到异常的地方,优先使用标准异常而不是自定义异常,这样会使你的代码更具可读性(因为标准异常大家都熟悉),同时也减少了异常类。

常见的标准异常有:

IllegalArgumentException,这个异常表示调用者传递了不合适的参数。一般在检测到参数不正确的时候我们可以抛出这个异常,或者是返回null值或false,结束方法。

NullPointerException,这个是我们最熟悉的异常了。如果调用者在某个不允许null值的参数中传递了null值,习惯的做法就是抛出NullPointerException异常。

IndexOutOfBoundsException,如果调用者在某个序列下标的参数中传递了越界的值,应该抛出的就是IndexOutOfBoundsException异常。比如访问超过数组下标的数组元素。

ConcurrentModificationException,这个异常被设计在java.util包中,用来表示一个单线程的对象正在被并发的修改。

UnsupportedOperationException,这个异常表示当前对象不支持所请求的操作。比如在实现类中没有实现接口定义的方法,就会抛出这个异常。

NumberFormatException,这个异常表示数据格式有误,还有一个ArithmeticException异常,表示算术异常,比如在除法运算中传递了0作为除数。

还有我们使用的DAOException异常,表示访问数据库出了问题。

2.3 每个方法抛出的异常都要有文档

不仅要为每个方法建立文档,为每个方法抛出的异常建立完善的文档也是很有必要的。

始终要单独声明每个受检的异常,并且利用Javadoc的@throws标记,准确地记录产生每个异常的条件。如下所示:


/**

* Create a POIFSFileSystem from an InputStream

*

* @param stream the InputStream from which to read the data

*

* @exception IOException on errors reading, or on invalid data

*/

public POIFSFileSystem(final InputStream stream) throws IOException;

(注: @throws以前的写法是@ecxeption)

对于未受检异常,只要使用@throws标签记录下可能抛出的异常以及条件就可以了。但不要使用throws关键字将该异常包含在方法的声明中。这是因为使用throws关键字声明的异常必须在调用者的代码中进行捕获,而未受检的异常一般是不需要调用者捕获的。

2.4 记录所捕获到异常的有用信息

异常发生时,我们通常都会查看产生异常的原因来排查问题,所以在异常中包含尽可能有用的细节信息对我们解决问题有很大的帮助。还好,一般的异常都会记录异常产生的类名、方法名和发生异常的位置,以及大量的堆栈信息。

实际上仅有这些信息也不能完全满足我们的需求。有时候,我们通过异常能判断是操作数据库的时候出错了,但我们还不能确定是我们的SQL语句有错,还是没有操作权限,或者是数据库操作过程中发生了其它错误。这时候我们可能希望能看到执行的SQL语句或其它中间状态,记录这些信息也是很有必要的。有两种方式可以实现,一种做法是在捕获异常后再记录这些信息;另一种做法是在自定义异常类的构造器中引入这些信息。比如我们可以在IndexOutOfBoundsException异常中定义一个如下的构造器。


public IndexOutOfBoundsException(int lowerBound,int upperBound,

int index){

super("Lower bound:" + lowerBound + ", Upper bound:" +

upperBound + ", Index:" + index);

this.lowerBound = lowerBound;

this.upperBound = upperBound;

this.index = index;

}

以上代码摘自《Effective Java》,虽然JDK并没有这样做,但是Joshua Bloch还是极力推荐这种做法。

其实在SQLException里是有比较详细的信息记录的,看一下SQLException的源代码就会发现它有一个如下的构造器:


public SQLException(String reason, String sqlState, int vendorCode, Throwable cause)

这个构造器为我们详细的记录了异常发生的原因。Reason表示异常发生的原因;sqlState是XOPEN或者SQL:2003(一种规范)关于异常分类的代码;vendorCode是数据库特定的异常代码。

有了以上的信息之后,我们就可以通过SQLException中的getSQLState()和getErrorCode()方法来获得异常更精确的异常信息。

2.5 努力使失败保持原子性

失败的原子性,意思就是失败的方法调用应该使对象保持在调用之前的状态。摘自《Effective Java》

这一点听起来比较抽象,但是实际上很容易理解。试想想一种情况,当我们捕获了受检的异常之后,我们并不会终止我们的代码继续执行,而试图从这个异常中恢复。但是有一个对象的值却在异常中被改变了,这样我们的下一步操作可能取到的是一个不期望得到的值。举个例子,假如我们要统计进行了某种操作的分享,用下面的代码,或许在我们获取分享的时候就失败了,但是计数器却进行了加1,很明显不是我们期望的结果。


int count=0;

for(long baskItemId:arraylist){

count++;

BaskItem baskItem=this.getBaskItemById(baskItemId,buyerId);

if(baskItem!=null){

…;

}

}

为了避免异常所产生的对象改变,我们可以有很多预防办法。

① 在执行操作之前对参数进行校验,如果参数不对就抛出异常,避免后续的操作改变对象的值。(实际上在一个方法开始之前进行参数校验是很好的做法,不仅可以避免异常带来的对象改变,还可以避免不必要的开销。)

② 调整处理顺序,使得任何可能引起对象改变的操作在可能产生异常的调用之后。

③ 在捕获异常之后编写一段恢复的代码,进行回滚。

④ 拷贝一份对象,在对象的拷贝上执行操作。当执行完毕后用拷贝的对象替换原来的对象。

当然,并不是针对任何异常都需要保持失败的原子性,如果方法抛出了某种错误,使失败保存原子性就没有多大意义了。比如出现OutOfMemoryError,就没必要再从错误中恢复了。

2.6 不要一次捕获所有异常

在我们的代码中,经常会看到如下一劳永逸的代码。


try {

…… ……//抛出AException

…… ……//抛出BException

…… ……//抛出CException

} catch (Exception e) {

log.error("XXX error", e.getCause());

}

这段代码的特点是用一个catch子句捕获了所有的异常,看上去省事很多。实际上是存在缺陷的。一是可能针对不同的异常有不同的恢复措施,而这里的代码让我们无法区分异常,也就无法针对性的恢复。二是这种方式掩盖了代码中的RuntimeException异常,通常出现RuntimeException属于编程错误,也就是这种方式掩盖了我们的编程错误。

针对一段代码中可能有多个调用会产生异常,我们可以分别捕获每种异常,进行不同的处理。

2.7 对可恢复的情况使用受检异常,对编程错误使用运行时异常

这一点在明确了受检异常和未受检异常的区别就不难理解了。受检异常是在编程中必须捕获的异常。我们捕获异常的目的就是在异常发生时希望采取一些措施来恢复。那如果我们希望调用者在调用我们的方法出错之后能有补救措施,我们就应该抛出受检异常。而对于一些编程错误,我们希望的是调用者能够在代码中就避免,这时候就应该抛出未受检的异常RuntimeException,提示调用者改进代码。比如前面提到的IndexOutOfBoundsException异常,出现这类异常,就应该考虑在代码中避免数组越界了。

2.8 在finally块中释放资源

针对有些操作,比如IO、Socket或者数据库操作,发生异常的时候,我们还需要正确释放占用的资源。一般我们在finally块中完成资源的释放,值得注意的是在finally块中的子句也可能会出现异常,我们在编码的时候要尽量避免出现这种异常。如下所示:


try{

……

reader = new BufferedReader(new InputStreamReader(

new FileInputStream(file)));

for (String record = reader.readLine(); record != null {

……

}

} catch (FileNotFoundException e) {

log.error("error while reading file " + file.getName());

} catch (IOException e) {

log.error("error while reading file " + file.getName() + e);

} catch (Exception e) {

log.error("baskitem 任务失败…");

} finally {

try {

reader.close();

} catch (IOException e) {

log.error("error while closeing file " + file.getName());

}

}

时间: 2024-09-19 08:51:51

Java中异常处理方法总结的相关文章

JAVA中toString方法的作用

以下是对在JAVA中toString方法的作用进行了详细的分析介绍,需要的朋友可以参考下   因为它是Object里面已经有了的方法,而所有类都是继承Object,所以"所有对象都有这个方法". 它通常只是为了方便输出,比如System.out.println(xx),括号里面的"xx"如果不是String类型的话,就自动调用xx的toString()方法 总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法  回答补充:写这个

java中除了方法可以使用throws关键词抛出异常外,类可以吗

问题描述 java中除了方法可以使用throws关键词抛出异常外,类可以吗 java中除了方法可以使用throws关键词抛出异常外,类可以吗,可不可以抛来抛去而不处理它 解决方案 方法中如果需要抛出异常,就只能用throws异常的,那么调用这个方法的地方必须处理这个抛出的异常.如果调用了抛出异常的方法,要么使用try-catch处理该异常,要么直接抛给它的上层调用者.最后如果都没有处理,那么运行时就由JVM进行处理,那么程序就会出错了. 解决方案二: 类不可以抛出异常,方法抛出的异常交给调用者处

在java中一个方法抛两个异常会怎样

问题描述 在java中一个方法抛两个异常会怎样 在一个java方法中同时用throw和throws抛出异常.会有不良的影响么.会不会有一个一个异常抛不出去的情况发生啊?求大神帮帮忙 解决方案 他俩用的地方不一样,throws是写在方法后面,其他地方调用时,要捕获,throw是在方法内直接抛出,可以一起用throw触发了,估计就到不了throws的捕获了,看你具体怎样用的 解决方案二: 方法声明时可以抛出异常,但是并不代表调用这个方法时会发生异常. 测试要构造异常条件才能走入异常分支的啊.例如:

java中同一个方法可以被几个类或者被几个对象共享

问题描述 java中同一个方法可以被几个类或者被几个对象共享 java中同一个方法可以被几个类或者被几个对象共享java中同一个方法可以被几个类或者被几个对象共享 解决方案 方法本质上是字节码序列,底层是指令集合.方法是通过对象调用的,对象是存储在堆中的,是所有线程共享的.方法是类型信息,不存在共享这个说法. 解决方案二: java 中的方法都是在类中的,而对于你说的方法可以被几个类或者几个对象共享,这个问题问的没有多大意义,方法属于类的方法,不会被共享,可以被子类复写和调用. 解决方案三: j

跪求!!!java中,方法能调用自身吗?

问题描述 跪求!!!java中,方法能调用自身吗? 为实现对Student类中age的封装,使用了setage(),想实现当年龄不在一定范围内是提示输入错误并重新输入,重新输入我想通过再次运行setage()来实现,用this会出错,可是应该怎样操作?代码如下:public void setAge(int age) { if(this.age<15) { System.out.println("输入有误,请重新输入"); //this.setAge(int age); } els

java基础-求问关于java中异常处理的问题。。

问题描述 求问关于java中异常处理的问题.. 不是很理解java异常处理中的try catch语句块,比如说我的catch语句块中是一个System.out.println("caught IOEexception":e.getMessage()),那么这个catch语句起到的作用就是提示我一下哪个地方有异常吗?然后我再去它提示我发生异常的地方去修改我的程序? 那么如果我的catch语句里面什么都没写,只是一个catch{}:由于写了try catch语句块编译一定会通过,那岂不是说

java中toString方法干嘛用的

问题描述 java中toString方法干嘛用的 java中toString方法干嘛用的 为什么打印一个对象会自动调用该对象的 toString方法 该方法的应用场景是什么? 解决方案 你打印的时候,打印的对象必须是string,如果你对象不是这个类型,输入就会把这对象强制转换为string 不然他会报错. 解决方案二: 解决方案三: 百度就有,这个问题不是很难,百度资料很详细. 解决方案四: Java中的toString方法关于Java中的toString()方法java toString()

java类的问题-关于java中的方法的区别

问题描述 关于java中的方法的区别 我是java菜鸟,想问一个问题关于 public static void main (String [] args){} 和static public void main (String [] args){} 的区别是什么?在jvm中是如何执行的? 解决方案 应该是没区别的吧 你要看区别 先分别编译后 后看看编译后的内容的区别吧 很多代码经过编译后效果是一样的

java阻塞方法-java中阻塞方法有accept和rievce还有其他什么阻塞方法吗

问题描述 java中阻塞方法有accept和rievce还有其他什么阻塞方法吗 java中阻塞方法有accept和rievce还有其他什么阻塞方法吗 如何判断一个方法是不是阻塞方法呢? 解决方案 涉及到流读取的都阻塞吧? 还有就是线程挂起的sleep和wait方法了.