4.Catching Multiple Exceptions in Java 7

Catching Multiple Exceptions in Java 7
在java7中,在同一个catch块中是可以捕获到多个不同的异常的.这被称之为多次捕获.
在java7之前你可能这样写

try {

    // execute code that may throw 1 of the 3 exceptions below.

} catch(SQLException e) {
    logger.log(e);

} catch(IOException e) {
    logger.log(e);

} catch(Exception e) {
    logger.severe(e);
}

你可以看到, SQLEXCEPTION AND IOEXCEPTIOn的处理方式相同,但是你需要些两个不同的catch块来处理,在java7中你可以使用这样的语法:

try {

    // execute code that may throw 1 of the 3 exceptions below.

} catch(SQLException | IOException e) {
    logger.log(e);

} catch(Exception e) {
    logger.severe(e);
}

注意:两个异常使用|分割开,声明的多个异常处理会通过同一个catch接收.

Exception Hierarchies 异常的继承结构

在java或c#中,异常被分类到继承结构体系,异常继承结构的有点是你可以使用一个特定的异常,你能自动捕获该异常下所有的子类异常.换个说法,你能捕获一个特定异常也能捕获该异常下面所有的子类异常.例如FileNotFoundException ,你能捕获IOException捕获,也能使用FileNotFoundException.

多catch块
你可能已经知道使用多catch块.这是在try…块发生多种异常情况下的处理.但是,多个捕获块,也可用于try块内的所有抛出的异常情况是相同的类型或类型的子类.

try{
    //call some methods that throw IOException's
} catch (FileNotFoundException e){
} catch (IOException e){
}

按照异常的继承结构,上述代码是FileNotFoundException先被捕获,如果是IOException则被第二个捕获.

throws 语法
如果一个方法能抛出一具体的异常A,或者该异常的子类,可以使用方法声明 抛出A或者A的子类

public void doSomething() throws IOException{
}

public void doSomething() throws IOException, FileNotFoundException{
}

设计异常的继承结构
当设计一个API或者app时,为之设计一个异常体系结构是一个好主意.例如,Mr Persister(注:作者项目), 我们的持久层/ORM API的基础异常是 PersistenceException.这个基础异常能捕获和处理
Mr.Persister抛出的所有异常.

如果你需要更多粒度的异常抛出,例如你可能以为异常很难处理,需要增加一个子类异常到应用的基础异常库.可以选择的方法是要么捕获一个确切的异常要么捕获基础异常.在MrPersister项目中,我们增加了 ConnectionOpenException, QueryException, UpdateException, CommitException和ConnectionCloseException作为PersistenceException异常的子类.如果Mr Persister的用户想要处理ConnectionOpenException与QueryException区分开,他们可以捕获这些具体的异常使用具体的操作.如果不是,用户可以捕获PersistenceException处理所有异常.

你能通过更多等级的异常体系结构来区分所有的异常类型,例如,你可以区分一个异常是因为url错误,还是由于数据库没有运行.这个案例中,你能创建两个异常DatabaseURLException和DatabaseNotRespondingException,他们都扩展自ConnectionOpenException.用户可以捕获这两种不同的异常.

总结
这章节我们了解了异常的体系结构通过创建子类.区分子类用来捕获和处理这些细分的异常,你也能创建细分的异常.

Checked or Unchecked Exceptions?

在java中有两种基础异常类型:Checked异常和unchecked异常(译者:可以类比为JVM需要强制执行异常处理/不需要强制执行).c#只有unchecked异常.他们的区别在于:
1.checked 异常必须显式的捕获或传播通过 try-catch-finally异常处理.unchecked异常不必须.他们不需要显式的捕获.
2.checked异常继承自java.lang.exception. unchecked exceptions 继承自 java.lang.RuntimeException.

关于checked和unchecked有许多的争论,是否应该使用checked异常,我将通过许多争论提纲来说明,在我做之前,让我们先明确下:
checked和unchecked异常都是功能等效的.如果无法的unchecked异常的情形那么对于checked异常同样也无能为力.

不论你选择的是checked或unchecked,这都是一个个人或组织的习惯,跟功能好坏无关.

一个简单案例
在讨论这两种的优点和缺点时,我将先告诉你他们的不同用法,下面是抛出一个checked异常

    public void storeDataFromUrl(String url){
        try {
            String data = readDataFromUrl(url);
        } catch (BadUrlException e) {
            e.printStackTrace();
        }
    }

    public String readDataFromUrl(String url)
    throws BadUrlException{
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and return
        //it as a String instance.

        return data;
    }

       public class BadUrlException extends Exception {
        public BadUrlException(String s) {
            super(s);
        }
    }

storeDataFromUrl()方法有两种选择,要么捕获要么传播给访问栈

//传播给访问栈
   public void storeDataFromUrl(String url)
    throws BadUrlException{
        String data = readDataFromUrl(url);
    }

现在,让我们看一下unchecked 异常的处理,首先让异常类继承 java.lang.RuntimeException

public class BadUrlException extends RuntimeException {
        public BadUrlException(String s) {
            super(s);
        }
    }

    public void storeDataFromUrl(String url){
        String data = readDataFromUrl(url);
    }

    public String readDataFromUrl(String url) {
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and
        //return it as a String instance.

        return data;
    }

storeDataFromUrl()方法可以选择捕获异常,但不是必须的了,也不是必须要传播.

什么时候checked什么时候unchecked?

我们看到了两种处理方式,让我们好好讨论下.
许多java书籍建议你使用checked异常为所有 可恢复应用程序,使用unchecked异常处理应用程序不可恢复的场景.实际上大部分的应用都需要恢复包括空指针异常,无效参数异常或者其他unchecked异常. 行动/交易失败将中止,但应用程序必须活下去,准备下一步行动/事务服务。这通常是合法的关闭应用程序.例如,如果配置文件丢失了,应用程序不能做任何事情,这将导致程序关闭.

我的建议是你要么使用checked异常要么使用unchecked异常.混合异常类型常常导致迷惑和不一致的使用.当然你应该务实一点,在具体情况具体分析.

下面的列表是最常见的争论关于checked和unchecked异常.最常见的区别是pro-checked=con-unchecked,pro-unchecked=con-checked.因此
1.高级的检查异常
编译器强制捕获或传播检查异常是为了不让开发者忘记处理异常.
2.非检查异常是编译器不强制开发者捕获和传播异常.
3.高级非检查异常
检查异常被传播到访问栈顶层方法,因为这些方法需要强制声明抛出他们声明的异常.
4.当一个方法没有声明非检查异常,他们一旦出现异常将很难处理.
5.检查异常的声明成为方法声明的一部分,增加一个或删除一个异常声明在需要变更方法声明时会很困难.

争论一

try{
   callMethodThatThrowsException();
catch(Exception e){
}

上面这种情况,强制捕获或传播异常让开发者粗心的处理风险,只会让开发者忽略错误.

争论二
非检查异常很容易让开发者忘记异常处理,因为编译器不强制开发者处理.
没有比粗心的异常处理更糟糕的,当强制进行异常处理的时候.
在一个最近的大项目里,我们决定使用非检查异常.我个人的经验是,当使用非检查异常的方法趋向于抛出异常.我常常合理的有意识的考虑异常.不仅仅是检查下异常.我都会考虑一个方法的异常处理.

许多标准java API方法没有声明任何可检查异常仍然可能抛出非检查异常例如空指针和无效参数.你的应用程序需要处理这些非检查异常.你可以考虑这些检查异常很容易忘记处理非检查异常的情况,因为他们不需要声明.

argument3
传播到访问栈顶层方法的可检查异常,因为方法需要声明抛出异常,所以在顶层的方法要声明的异常是一种底层异常,例如.

 public long readNumberFromUrl(String url)
    throws BadUrlExceptions, BadNumberException{
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }

你能看到readNumberFromUrl()方法需要声明BadUrlException和BadNumberException,因为他的访问栈方法抛出了异常.想想,如果成千上万的类里面的顶级方法需要声明多少种异常.

争论1:
异常声明聚合通常发生在实际的应用程序中.通常开发者使用异常包装器代替,例如:

  public void readNumberFromUrl(String url)
    throws ApplicationException{
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(e);
        } catch (BadNumberException e){
            throw new ApplicationException(e);
        }
    }

你能看到readNumberFromUrl()方法声明了ApplicationException.而异常BadUrlException和BadNumberException被捕获并包装到一个通用的ApplicationException.这就是异常包装避免异常声明聚合.我个人的意见是,如果你所做的是包装一个异常,而不是提供附加的信息,你为什么要包装他?try-catch块附加一个代码而不做其他.使得这些异常成为unchecked 异常更容易,下面是以unchecked版本:

 public void readNumberFromUrl(String url){
        String data = readDataFromUrl(url);
        long number = convertData(data);
    }

包装一个unchecked异常如果你想.下面是一个包装版本,注意,方法签名并不声明抛出的异常类型:

 public void readNumberFromUrl(String url)
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        } catch (BadNumberException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        }

另外一个常用技术是避免异常声明传播到访问栈而创建的一个基础异常声明类.所有的异常捕获都是该基础异常的子类.所有的方法抛出异常仅需要声明throw一个基础异常, 一个方法抛出基类的子类:

 public long readNumberFromUrl(String url)
    throws ApplicationException {
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }


    public class ApplicationException extends Exception{ }
    public class BadNumberException   extends ApplicationException{}
    public class BadUrlException      extends ApplicationException{}

注意子类异常也不再声明了 也不捕获和包装了,因为他们的超类已经将异常传播给访问栈了.
我的意见跟异常包装一样:如果所有的方法都声明抛出基类异常,为什么不让基类成为unchecked异常 保存一些try-catch块,抛出一个基类.

当一个方法不再声明非检查异常,但是他们可能抛出这种异常将会很难处理.不声明你就不知道什么异常会抛出.你也许不会知道如何处理,当然除了你已经通过代码处理他们那些可能抛出的异常.
争论1:
在大多数情况下你,对于异常的处理你除了显示打印错误信息给调用者,不能做其他,写一条日志,回滚事务.不管什么异常发生在很多情况下处理他们是用相同的方式.因为应用程序通常有一些中央和通用的异常处理代码.因此知道实际发生了什么可能抛出的异常就没那么重要了.

总结
我通常赞成检查异常但是最近我凯撒改变我的主意.个人喜欢Rod johnson,anders hejlsberg,joshua bloch和其他人让我重新思考真实的益处来自检查异常.最近我们尝试使用unchecked异常在大项目上,他们工作很好.异常处理集中在一部分类.这里我们必须做本地异常处理代替传播异常到主异常处理代码.但是这不是非常多.我们的代码可读性更高,没有try-catch快.在另一方面,这里有更少的catch-rethrow try-catch块在代码里使用检查异常.我将提醒所有使用unchecked异常的人,至少使用try.我说明下原因:
1.非检查异常不需要杂乱的代码使用非必要的try-catch块.
2.非检查异常不需要杂乱的方法头声明去传播异常.
3.你很容易忘记处理非检查异常的论证是这不适合我的经验.
4.非检查异常避免了版本升级的问题.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值