Google Guava之简化异常和错误的传播与检查
文中所述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 实例或者是X1 、X2 实例时才抛出并向上传递 |
throwIfInstanceOf(Throwable, Class<X extends Throwable>) throws X:void |
异常是X 的实例时才抛出 |
throwIfUnchecked(Throwable):void |
异常是非受检异常(RuntimeException 或Error 的实例)时才抛出 |
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
,用于传播异常。
注:关于Throwable
和Exception
,由于Throwable
是JDK所有异常的父类,通过限定X
是Throwable
的子类即可囊括所有的错误类型,一般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
中,主要调用两个方法,propagateIfInstanceOf
和propagateIfPossible
,propagateIfInstanceOf
然后调用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
,是用来检查异常是否是非受检异常,若是的话则抛出向上传递,不然则无视该异常。可见throwIfInstanceOf
和propagateIfPossible
的区别主要在于前者在判定异常类的时候仅仅判定该异常是否是某个类的子类,是的话就抛出向上传递,否则就无视,而后者首先会判断异常是否是某个类的子类,是的话就抛出向上传递,否则判断该异常是否是非受检异常,是的话也抛出向上传递,否则无视。
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 形式 |