如何在Java中包装已检查异常但保留原始运行时异常

33

我有一些代码可能会抛出已检查异常和运行时异常。

我想捕获已检查异常并将其包装为运行时异常。但如果抛出RuntimeException,我不必包装它,因为它已经是运行时异常了。

我现在有一个解决方案,但有一些额外的开销,而且不太“整洁”:

try {
  // some code that can throw both checked and runtime exception
} catch (RuntimeException e) {
  throw e;
} catch (Exception e) {
  throw new RuntimeException(e);
}

有没有更优雅的方法?


6
就是这样。唯一的改进是一个高阶函数,接受一个lambda作为"try"体,并用这个逻辑进行包装。你可以查看这个主题:https://dev59.com/210Z5IYBdhLWcg3wnBRS - Marko Topolnik
我相信这是更优雅的做法。 - Bnrdo
8个回答

33
我使用“盲目”重新抛出来传递已检查的异常。我在通过Streams API时使用它,因为我不能使用抛出已检查的异常的lambda表达式。例如,我们有ThrowingXxxxx函数接口,因此可以通过已检查的异常传递。
这使我能够在调用者中自然地捕获已检查的异常,而不需要知道被调用方必须通过不允许已检查的异常的接口进行传递。
try {
  // some code that can throw both checked and runtime exception

} catch (Exception e) {
  throw rethrow(e);
}

在调用方法中,我可以重新声明已检查的异常。
public void loadFile(String file) throws IOException {
   // call method with rethrow
}

/**
 * Cast a CheckedException as an unchecked one.
 *
 * @param throwable to cast
 * @param <T>       the type of the Throwable
 * @return this method will never return a Throwable instance, it will just throw it.
 * @throws T the throwable as an unchecked throwable
 */
@SuppressWarnings("unchecked")
public static <T extends Throwable> RuntimeException rethrow(Throwable throwable) throws T {
    throw (T) throwable; // rely on vacuous cast
}

处理异常有很多不同的选项。我们使用其中的几个。

https://vanilla-java.github.io/2016/06/21/Reviewing-Exception-Handling.html


2
所以实际上这会在运行时抛出IOException(来自“rethrow”),但编译器认为它是未经检查的异常(因为您说过您会以那种方式进行转换,即使转换被擦除了)? - Thilo
2
@Thilo 在运行时,对于JVM来说,已检查和未检查之间没有区别,并且有一些方法可以在运行时“愚弄”编译器。使用泛型进行转换是我认为最简单的方法。 - Peter Lawrey
9
“Sneaky”是这种情况的半官方名称 :) - Marko Topolnik
5
@AlikElzin-kilaka 是的,这种语法只适用于Java 8中的新推理规则。在Java 7中需要使用两个方法的较长习惯用法。 - Marko Topolnik
1
@PeterLawrey throw rethrow(e); 为什么要多一个 throw?但在字节码中,如果删除 throw,它将被替换为 ATHROW 并用 POP 替代,因此大小相同。 - Mikhail Boyarsky
显示剩余13条评论

20

Guava的Throwables.propagate()方法正是这样实现的:

try {
    // some code that can throw both checked and runtime exception
} catch (Exception e) {
    throw Throwables.propagate(e);
}

更新:此方法现已弃用。有关详细说明,请参见此页面


无关:今天我(希望)能够达到传奇黄金...只是想感谢所有教给我很多东西的人... - GhostCat

4

不完全正确。

如果你经常这样做,你可以将其封装成一个助手方法。

static RuntimeException unchecked(Throwable t){
    if (t instanceof RuntimeException){
      return (RuntimeException) t;
    } else if (t instanceof Error) { // if you don't want to wrap those
      throw (Error) t;
    } else {
      return new RuntimeException(t);
    }
}

try{
 // ..
}
catch (Exception e){
   throw unchecked(e);
}

catch (Exception e) 更改为 catch (Throwable e) - Jason S
@JasonS:如果你想捕获Error(这不是推荐做法),你也可以这样做。我会让它们未被捕获。编译器不会对此抱怨。 - Thilo
我认为重点在于,如果你只捕获Exception,那么没有理由使用Throwable并针对Error进行特殊处理。 - OrangeDog
@OrangeDog 可能太过于低耦合了。这个方法的适用范围比示例调用代码所需的范围更广泛。但是对于许多异常处理工具来说都是如此,例如已经提到的Guava Throwables.propagate或Peter的rethrow - Thilo

2

我有一个特别编译的 .class 文件,其中包含以下内容:

public class Thrower {
    public static void Throw(java.lang.Throwable t) {
        throw t;
    }
}

它只是有效的。Java编译器通常会拒绝编译这个,但字节码验证器完全不在乎。

该类的使用方式类似于Peter Lawrey的答案:

try {
  // some code that can throw both checked and runtime exception

} catch (Exception e) {
    Thrower.Throw(e);
}

5
你是如何编译它的? - shmosel
2
我编辑了javac源代码以去除检查,编译了javac,并使用该javac编译.class文件。生成的.class文件可以被正常javac编译的.java编译文件引用。 - Joshua
哇,这不是我预期的。文件在哪里可以找到? - shmosel
这是指令。我不确定,但我认为博客作者是从我的原始指令中获得的,该指令位于Sun旧的Java错误存储库上。http://blog.bangbits.com/2009/08/tweaking-javac-leniency.html 如果错误存储库仍然存在,则.class文件在那里。 - Joshua
1
比彼得的答案更狡猾。我喜欢它。 - Thilo

1
你可以使用 instanceof 运算符来重写相同的内容。
try {
    // some code that can throw both checked and runtime exception
} catch (Exception e) {
    if (e instanceof RuntimeException) {
        throw e;
    } else {
        throw new RuntimeException(e);
    }
}

然而,你的解决方案看起来更好。

1
问题在于Exception太过宽泛。您应该明确知道可能的已检查异常是什么。
try {
    // code that throws checked and unchecked exceptions
} catch (IOException | SomeOtherException ex) {
    throw new RuntimeException(ex);
}

这种方法行不通的原因揭示了更深层次的问题,需要解决:
如果一个方法声明它会抛出异常(throws Exception),那么它过于笼统。对调用者来说,仅知道“可能出现问题”是没有用处的。该方法应该使用有意义的特定异常类别层级结构或适当时使用未经检查的异常。
如果一个方法抛出太多不同类型的已检查异常,则它过于复杂。它应该被重构为多个较简单的方法,或根据情况将异常排列在合理的继承层次结构中。
当然,也可能有例外情况。如果一个方法被某种横跨式框架(如JUnit、AspectJ或Spring)所使用,而不是组成其他人使用的API,则声明该方法抛出异常(throws Exception)是完全合理的。

1
他的方法并没有声明它会抛出异常,他想要优雅地捕获如果调用栈下面的某个方法抛出异常,这可能包括由他直接调用的方法声明的任何类型的异常,或者其他任何可能出错的东西,比如NPE等。这是完全合理的。 - user467257
@user467257 是的,我理解原帖。我想指出的是,如果“try”代码是一个抛出异常的方法,那么我的建议模式将无法工作,但这表明该方法设计得不好。 - OrangeDog

1

我通常使用相同类型的代码结构,但在极少数情况下,将其压缩成一行,这时候三元操作符实际上能够使代码更好:

try {
  // code that can throw
}
catch (Exception e) {
  throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
}

这不需要额外的方法或 catch 块,这就是我喜欢它的原因。

0

使用 lombok 可以通过在方法上添加简单的注解来处理此问题

示例:

import lombok.SneakyThrows;

@SneakyThrows
void methodThatUsusallyNeedsToDeclareException() {
    new FileInputStream("/doesn'tMatter");
}

在这个例子中,该方法应该声明throws FileNotFoundException,但是使用@SneakyThrows注释后,就不需要了。
实际上,在幕后发生的事情与同一问题的高评分答案一样,lombok做了相同的技巧。
任务完成!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接