Google Guava之简化异常和错误的传播与检查

摘要:
文中所述Guava版本基于29.0-jre,文中涉及到的代码完整示例请移步Github查看。JDK中的异常JDK中把程序中可能出现的异常、错误统一了起来。对于这样的要求,我们可以按照前一小节的写法进行异常判定然后处理,同时也可以借助Guava的Throwables类,Throwables类为我们提供了方便传递异常的方法。

文中所述Guava版本基于29.0-jre,文中涉及到的代码完整示例请移步Github查看。

JDK中的异常

JDK中把程序中可能出现的异常、错误统一了起来。

此处输入链接的描述

所有的异常、错误都是Throwable的子类,图中红色块的异常类是受检异常,表明在程序中出现这些异常时,需要我们通过try...catch...语句处理,异常发现的过程是在编译期进行的。而蓝色块的异常类是非受检异常,编译期间JDK不能发现这些异常,只有在运行的时候这些异常才会出现,所以也被称为运行时异常

JDK中提供了基本的异常类型和异常处理机制,但是在实际编码过程中,基础的异常可能不能满足我们的需求,这个时候对异常的拓展和对异常处理机制的包装便丰富了起来。

丰富异常处理

基础异常类型的拓展

JDK中提供的受检异常和非受检异常种类太少,虽然我们可以通过语义靠近(就是把所有的异常类型强制归类为JDK的几种异常类中)来让少量的异常支撑业务需求。但是为了让异常信息更加清晰易懂,还是建议拓展异常类的数量。
拓展异常类的方法比较简单,只需模仿JDK对某个异常类的定义,修改类名和类内部的部分信息即可,然后就可以在自己的代码中使用。

简单异常处理机制

最简单的异常处理机制莫过于直接使用try...catch...语句包裹代码块,捕获到异常打印异常信息然后根据业务情况决定下一步的处理。

public class ClassifyException {

    private final static double THE_SECOND_SPEED = 11.2;

    public void flyToMoon(double speed) throws Exception {
        if (speed < 0) {
            throw new ParameterException("speed wrong");
        }
        if (speed >= THE_SECOND_SPEED) {
            System.out.println("fly to moon");
        } else {
            throw new PermissionException("speed too slow");
        }
    }

    public static void main(String[] args) {
       ClassifyException classifyException = new ClassifyException();
       try {
           classifyException.flyToMoon(1);
       }  catch (Exception e) {
           System.out.println(e.getMessage());
       }
    }
}

但是某些情况下我们可能需要根据返回异常的类型不同决定下一步的动作。

public static void main(String[] args) {
    ClassifyException classifyException = new ClassifyException();
    try {
       classifyException.flyToMoon(1);
    } catch (ParameterException e) {
       System.out.println("parameter");
    } catch (PermissionException e) {
       System.out.println("permission");
    } catch (Exception e) {
       System.out.println(e.getMessage());
    }
}

但是上述多个异常捕捉块让代码看起来特别繁琐丑陋,如何写出干净优雅的代码是所有编程人员的追求,上述代码片段可以改写为。

public static String classifyException(Exception e) {
    if (e != null) {
        if (e instanceof ParameterException) {
            return "parameter";
        } else if (e instanceof PermissionException) {
            return "permission";
        } else {
            return e.getMessage();
        }
    }
    return "null";
}


public static void main(String[] args) {
   try {
       classifyException.flyToMoon(1);
   } catch (Exception e) {
       System.out.println(classifyException(e));
   }
}

有些时候某几种类型的异常可以被看做同一种异常情况,可以统一处理时,可以借用在JDK 1.7才引入的多重异常捕获机制来处理。

public class ClassifyException {

    private final static double THE_SECOND_SPEED = 11.2;

    public void flyToMoon(double speed) throws Exception {
        if (speed < 0) {
            throw new ParameterException("speed wrong");
        }
        if (speed >= THE_SECOND_SPEED) {
            System.out.println("fly to moon");
        } else {
            throw new PermissionException("speed too slow");
        }
    }
    
    public static void main(String[] args) {
       ClassifyException classifyException = new ClassifyException();

        try {
            classifyException.flyToMoon(1);
        } catch (ParameterException | PermissionException e) {
            System.out.println("self exception");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

如果需要更优雅的对异常分类处理,我们可以自定义多个类别的异常接口,然后让自定义的异常实现该接口,最后在捕获异常判定异常种类的时候,需要根据异常接口的类别判断即可。

异常的传递

某些情况下,对于某些异常类型,我们不需要处理,要做的仅仅是捕获然后向外传递,而某些类型的异常则需要自己处理。对于这样的要求,我们可以按照前一小节的写法进行异常判定然后处理,同时也可以借助Guava的Throwables类,Throwables类为我们提供了方便传递异常的方法。

方法说明
propagateIfPossible(Throwable, Class<X extends Throwable>) throws X:void异常是RuntimeException实例、Error实例或者是X实例时才抛出并向上传递
propagateIfPossible(Throwable, Class<X1 extends Throwable>, Class<X2 extends Throwable>) throws X:void异常是RuntimeException实例、Error实例或者是X1X2实例时才抛出并向上传递
throwIfInstanceOf(Throwable, Class<X extends Throwable>) throws X:void异常是X的实例时才抛出
throwIfUnchecked(Throwable):void异常是非受检异常(RuntimeExceptionError的实例)时才抛出

propagateIfPossible

propagateIfPossible方法有两个重载,由于参数进行了限定(X extends Throwable),所以该方法默认会把非受检异常抛出并传递。这和我们的普遍用法相符,因为对于非受检异常一般会被认为代码所不能处理的,应该向上抛出由外层处理。

public static void main(String[] args) throws Exception {
   ClassifyException classifyException = new ClassifyException();

    try {
        classifyException.flyToMoon(1);
    } catch (Exception e) {
        Throwables.propagateIfPossible(e, ParameterException.class, PermissionException.class);
    }
}

上面这段代码和之前的区别主要是main方法增加了异常抛出throws Exception,用于传播异常。

注:关于ThrowableException,由于Throwable是JDK所有异常的父类,通过限定XThrowable的子类即可囊括所有的错误类型,一般Error类用于表示影响系统正常运行的错误,这种错误发生时一般表示系统出现了重大错误需要退出,而Exception则表示未考虑到的异常情况,异常发生时通过妥善的处理即可恢复系统的行为。

throwIfInstanceOf

throwIfInstanceOf和方法参数和propagateIfPossible看似没有什么差别,而且完成的功能也似乎相同,都是判定异常是否是某个Throwable子类的实例,是的话就向外传递,否则就什么都不做。Guava为何提供如此相似的方法呢?要探究这两个方法的不同,我们需要分析下Guava中关于这两个方法的编写。

public static <X extends Throwable> void propagateIfPossible(@Nullable Throwable throwable, Class<X> declaredType) throws X {
    propagateIfInstanceOf(throwable, declaredType);
    propagateIfPossible(throwable);
}
 
    
public static <X extends Throwable> void throwIfInstanceOf(Throwable throwable, Class<X> declaredType) throws X {
    Preconditions.checkNotNull(throwable);
    if (declaredType.isInstance(throwable)) {
        throw (Throwable)declaredType.cast(throwable);
    }
}

propagateIfPossible中,主要调用两个方法,propagateIfInstanceOfpropagateIfPossiblepropagateIfInstanceOf然后调用throwIfInstanceOf

public static <X extends Throwable> void propagateIfInstanceOf(@Nullable Throwable throwable, Class<X> declaredType) throws X {
    if (throwable != null) {
        throwIfInstanceOf(throwable, declaredType);
    }
}

propagateIfInstanceOf完成的功能是进行异常类型的判定,可见propagateIfPossible的一半功能就和throwIfInstanceOf的功能一样,propagateIfPossible的另一半是调用propagateIfPossible方法,是完成什么功能呢?

public static void propagateIfPossible(@Nullable Throwable throwable) {
    if (throwable != null) {
        throwIfUnchecked(throwable);
    }
}

propagateIfPossible方法最终调用的是throwIfUnchecked,是用来检查异常是否是非受检异常,若是的话则抛出向上传递,不然则无视该异常。可见throwIfInstanceOfpropagateIfPossible的区别主要在于前者在判定异常类的时候仅仅判定该异常是否是某个类的子类,是的话就抛出向上传递,否则就无视,而后者首先会判断异常是否是某个类的子类,是的话就抛出向上传递,否则判断该异常是否是非受检异常,是的话也抛出向上传递,否则无视。

throwIfUnchecked

非受检异常就抛出向上传递。

过期的 Throwables.propagate()

在Guava的v20.0版本,Throwables.propagate(Throwable)方法已被弃用,关于为什么弃用,Guavau官方从多个方面举出多个示例来说明原因,有兴趣可移步为什么弃用Throwables.propagate(Throwable)了解更多细节。

异常链信息

Guava的Throwables类也提供了获取异常链信息的方法。

方法说明
getRootCause(Throwable):Throwable获取异常堆栈
getCausalChain(Throwable):List<Throwable>获取异常堆栈,一般用于判定循环异常堆栈
getStackTraceAsString(Throwable):String获取异常堆栈信息的String形式
参考

免责声明:文章转载自《Google Guava之简化异常和错误的传播与检查》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C# BeginInvoke与EndInvoke的使用s-HR导出excel方式二下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

DBeaver下载驱动文件失败

今天,当DBeaver软件首次用于链接数据库时,驱动程序文件将被下载,如下图所示:上图显示下载驱动程序文件失败,并提示“无法解决库文件,请检查网络设置”。到目前为止,我们可以使用DBeaver正常操作数据。...

kibana数据操作

ES中的索引数据将存储为_分数:分数越高,匹配越好。即使Lucene使用了反向索引,计算搜索得分仍需要一些时间。②.— 筛选器上下文:使用筛选器参数时的执行环境,例如在布尔查询中使用Must _ Not或Filter。在筛选器上下文中,查询将回答此问题...

试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)

解决方法:iis应用程序池--˃高级设置--˃启用32位应用程序˂!body{font-family:"Verdana";font-weight:normal;font-size:.7em;color:black;}p{font-family:"Verdana";font-weight:normal;color:black;margin-top:-5px}b...

Debian忘记密码重置

我使用的系统是Debian8,但这种方法也适用于Debian7以上的系统。具体步骤是重新启动VPS。您可以使用“CTRL+ALT+DEL”按钮直接在面板或VNC上重新启动VPS,然后按图中的“e”按钮;在BIOS界面上,按“e”进入GRUB引导菜单,然后按“e”进入编辑;输入GRUB编辑红色框中的内容,并将“ro”替换为“rwinit=/bin/sh”;修改...

当微信小程序遇到AR(二)

当微信小程序遇到AR,会擦出怎么样的火花?期待与激动......通过该教程,可以从基础开始打造一个微信小程序的AR框架,所有代码开源,提供大家学习。注册地址=˃注册成功之后,需要下载微信小程序开发工具。下载地址=˃目前笔者的开发环境是:Windows10下载的微信小程序版本为:RCv1.0.2.1909111 打开,微信开发者工具之后,会看到如下的页面。...

部署springboot+vue项目文档(若依ruoyi项目部署步骤)

1: 部署Linux+nginx部署背景代码1.1因为我使用了idea工具进行开发,所以终端中的mvnclean包生成了相应的jar包。这个jar包可以在相应文件所在目录的目标中找到。linux服务器需要加载redis和nginx。redis存储缓存数据,nginx用于代理前端和后端服务。打包vue项目并将dist文件复制到tomcat的webapps目录中...