对java异常的辨论


  现在我将辩论的问题,是到底还要不要使用“检查型异常”。

  我们如果使用“检查型异常”,那么我们在调用有异常方法的时候,就不得不将这方法放入到try-catch块中去,而不论我们是否真的就要在这儿把这个异常给解决掉。一个可行的办法就是,我们如果不需要处理,那么我们可以捕捉后在catch块中使用e.fillInStackTrack()方法将它往上抛,而且还可以保持异常产生到捕捉的轨迹不丢失。不过就算是这样,很明显还是很麻烦,是一个效率低下的办法。我们每天得处理很多这样的异常,真是太麻烦了,所以,很多人反对使用“检查型异常”。
  使用“检查型异常”时很容易犯的一个错误就是丢失异常。当我们把一个有有异常声明的方法放到try块中去后,不管我们是否有catch捕捉到它,或是捕捉到后是否有处理,异常处理机制都会默认这个异常已经得到正确的处理了。如:
try {
} finally{
}

try{
} catch(Exception ex) {
//这儿并不做任何事情,异常就这样在这儿消失了,但是根本没有得到有效的处理。
}
  有人提出对“检查型异常”进行下面类似的包装:
class WrapCheckedException {
  void throwRuntimeException() {
    try {
       throw new AMyselfException("Where am I?");
    } catch(Exception e) { // Adapt to unchecked:
      throw new RuntimeException(e);
    }
  }
}
  这样我们调用这个方法的时候,不用把它放入try中去,异常也会被捕捉。而当我们需要在catch块中捕捉它并进行处理的时候,能够使用getCause()方法,如:
catch(RuntimeException re) {
        try {
          throw re.getCause();
        } catch(AMyselfException e) {
          e.printStackTrace();
        }
  不过我觉得这样的包装实在是非常的滑稽。因为这儿在方法中抛出的异常是一个运行期异常,我们以后调用这个方法的时候,根本不知道这个方法会抛出一个怎样的异常,那么我们又怎么会catch(AMyselfException e)呢?

  而“检查型异常”附加的那些异常说明(throws)虽然使程序多了一些代码,但当我们不仅仅是要报告发生了一个异常,而且我们还要根据发生了某某异常,而对异常进行处理的时候,这个特别的说明就对我们非常有用了。如果是“非检查型异常”的话,我们根本就不知道我们调用的方法会发生一个具体什么类型的异常,我们最多就是知道这个方法会抛出一个异常。这样的话,我们又如何对异常进行正确的处理?

  我们看到,对异常的附加说明是不能够取消的。如果我们一旦取消了这个对异常的说明,那么我们每次想对异常进行处理的时候,不得不再去方法中找到那个抛出异常的地点,看到底是抛出了一个什么具体类型的异常。如果这个方法是第三方库中的方法,而又没有对这个方法将抛出的异常的说明,那么你不得不去询问第三方库生产商。这时候,第三方有可能也需要去检查源代码,才知道那个方法到底抛出了什么类型的异常。可以看到,这样将会造成许多非常麻烦的后果。而如果我们在方法名尾部紧跟了一个对方法将会抛出的异常的说明,那么那么将很轻松的知道我们要处理的方法会抛出什么类型的异常。问题是现在的异常处理机制,每次调用有异常说明的方法的时候,都必须把这个方法放入try块中,这样实在是非常的烦恼。

  从上面我总结出来一个综合的异常处理方案:由于考虑到java的向后兼容性,我们现有的异常机制不能进行彻底的动摇。那么,我们可以通过在方法后新增加一个子句,用来说明那些方法中将要抛出的“非检查型异常”。这样的话,调用一个只有“非检”说明子句的方法,我们是不需要将之放入try块中去的。而我们想要更具体的处理异常的时候,我们也能将这方法放入try块中,然后进行捕捉。具体需要捕捉哪些异常呢?在方法的“非检”说明中都有,这样不是很方便吗?

  如果以后的异常处理机制进行了上面的改造,那么我们又有一个问题需要提出来。这个问题就是,什么时候我们需要在方法中抛出“检查型异常”?现在在网上也有很多争论,在我看到的中间,有很多都认为“检查型异常”可以不用,是一个java中的一大失败。但我不这么认为,可是我需要一个使用“检查型异常”的理由,给“检查型异常”一个存在的空间。

  下面有一个经常发生在我们代码中,但是却十分有趣的现象:
void foo() {

    InputStream in = null;
    OutputStream out = null;

    try {
        in = new FileInputStream("foo1.txt");
        out = new FileOutputStream("foo2.txt");

        // do some stuff with in/out

    } catch (IOException e) {
        println("something bad happened");
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch IOException(e) {
                println("whooops!");//very crazy!!!
            }
        }
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                println("whooops 2!");
            }
        }
    }
}

  在上面的代码中我们看到,如果文件没有成功关闭,那么我们还需要写一个try块来处理它,但是这个问题是不可能得到处理的。在这个问题中,我们需要的只是文件没有被关闭这个报告就行了,而且只能做到这样。那么我们完全可以用一个“运行期异常”来代替原来的这个“checked Exception”。这个例子也让我们看到一些人说“运行期异常”在JAVA中完全没有用武之地是错误的观点。

  使用“检查型异常”中有一个“bubbling up”的问题。例如:我有一个f()方法,throws exA,exB,exC,exD异常。常常我们偷懒写成throws Exception,这样做非常不好。

  使用“Checked Exception”还带来很多的抱怨。例如:我们在方法f()中throws Exception,但是在方法内部,我们并没有throw异常,甚至也没有必要throw异常,也许我们根本不需要,也许我们是为了以后可能发生做好防备。但是,我们在调用f()方法的时候,不管我们愿意还是不愿意,编译器都要求我们把这个方法放入try块中,真是烦恼至极,而且听说在sun的类库中还经常有这样的问题存在。


下面这些内容摘自thinking in java第三版:

Exception guidelines

Use exceptions to:

  1. Handle problems at the appropriate level. (Avoid catching exceptions unless you know what to do with them).

    在合适的地方处理异常(避免在不知道该如何处理的情竞下去捕捉异常)。
  2. Fix the problem and call the method that caused the exception again.

    解决掉问题,然后重新调用引发问题的方法。
  3. Patch things up and continue without retrying the method.

    修正一下问题,然后绕过那个方法再继续。
  4. Calculate some alternative result instead of what the method was supposed to produce.

    用一些别的计算结果,而不是那个方法将返回的结果。
  5. Do whatever you can in the current context and rethrow the same exception to a higher context.

    把当前你能做完的事情做完,然后把相同的异常抛到更高层。
  6. Do whatever you can in the current context and throw a different exception to a higher context.

    把当前你能做完的事情做完,然后抛一个不同的异常到更高层。
  7. Terminate the program.

    中止程序。
  8. Simplify. (If your exception scheme makes things more complicated, then it is painful and annoying to use.)

    简化(如果你的异常把事情搞得更复杂,那用起来将非常的烦恼和痛苦)。
  9. Make your library and program safer. (This is a short-term investment for debugging, and a long-term investment for application robustness.)

    使你的类库和程序更安装(这既是为调试做短期投资,也是为程序的健壮性做长期投资)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值