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

Posted

技术标签:

【中文标题】如何包装检查的异常但在 Java 中保留原始运行时异常【英文标题】:How to wrap checked exceptions but keep the original runtime exceptions in Java 【发布时间】:2017-02-04 18:33:15 【问题描述】:

我有一些代码可能会同时引发检查异常和运行时异常。

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

我的解决方案有点开销,而且不是“整洁”:

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

有什么更优雅的方法吗?

【问题讨论】:

就是这样。唯一的改进是一个高阶函数,它接受一个 lambda,它是 try 主体并用这个逻辑包装它。你可以看看这个话题:***.com/questions/31270759/… 我相信这是更优雅的方式。 【参考方案1】:

我使用“盲目”重新抛出来传递检查的异常。我已经使用它来通过 Streams API,在该 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

【讨论】:

所以这实际上在运行时抛出了 IOException(来自rethrow),但编译器认为这是一个未经检查的异常(因为你说你会那样转换它,即使转换被删除了)? @Thilo 在运行时,JVM 的检查和未检查之间没有区别,并且有几种方法可以“欺骗”在运行时工作的编译器。使用泛型的演员是我觉得最简单的。 偷偷摸摸。我喜欢。 :-)(是的,没有来自 lamdbas 的例外是不方便的)。 “Sneaky”是这个的半官方名称 :) @AlikElzin-kilaka 是的,此语法仅适用于 Java 8 中的新推理规则。Java 7 需要更长的具有两种方法的习语。【参考方案2】:

Guava 的 Throwables.propagate() 正是这样做的:

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

更新:此方法现已弃用。详细解释见this page。

【讨论】:

无关:今天我(希望)得到传说中的黄金......只是感谢所有让我学到很多东西的人......【参考方案3】:

不是真的。

如果你经常这样做,你可以把它塞进一个辅助方法中。

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)? @JasonS:你可以,如果你还想赶上Error(不推荐)。我只是让那些不被抓住。编译器不会抱怨。 我认为关键是没有理由使用 Throwable 并且如果您只捕获 Exception,则可以使用 Error 的特殊情况。 @OrangeDog 可能低耦合太多了,是的。该方法比示例调用代码需要的更广泛适用。但这对于许多异常处理实用程序来说都是一样的,例如已经提到的 Guava Throwables.propagate 或 Peter 的 rethrow【参考方案4】:

我有一个专门编译的 .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);

【讨论】:

你是怎么编译的? 我编辑了javac源代码敲出签出,编译了javac,并用那个javac编译了.class文件。生成的 .class 文件可以被普通 javac 编译的 .java 编译引用。 哇,不是我所期待的。该文件在某处可用吗? 这里是说明。我不确定,但我认为博客海报是从我对 sun 的旧 java bug 存储库的原始说明中获得的。 blog.bangbits.com/2009/08/tweaking-javac-leniency.html如果bug存储库仍然存在,那么.class文件就在那里。 比彼得的回答还要狡猾。我喜欢它。【参考方案5】:

您可以使用 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);
    

但是,您的解决方案看起来更好。

【讨论】:

【参考方案6】:

问题是Exception 太宽泛了。您应该确切地知道可能的检查异常是什么。

try 
    // code that throws checked and unchecked exceptions
 catch (IOException | SomeOtherException ex) 
    throw new RuntimeException(ex);

这不起作用的原因揭示了应该解决的更深层次的问题:

如果一个方法声明它throws Exception,那么它就太宽泛了。在没有进一步信息的情况下知道“可能会出错”对呼叫者来说毫无用处。该方法应该在有意义的层次结构中使用特定的异常类,或者在适当的情况下使用未经检查的异常。

如果一个方法抛出了太多不同种类的检查异常,那么它就太复杂了。它应该被重构为多个更简单的方法,或者应该根据情况将异常安排在合理的继承层次结构中。

当然,规则也有例外。声明一个方法 throws Exception 可能是完全合理的,如果它被某种横切框架(例如 JUnit 或 AspectJ 或 Spring)使用,而不是包含一个供其他人使用的 API。

【讨论】:

他的方法没有声明它会抛出异常,他想优雅地捕捉调用堆栈下方是否抛出异常,这可能包括他直接调用的方法声明的任何类型异常,或任何东西其他像 NPE 或任何可能出错的东西。完全合理。 @user467257 是的,我了解 OP。我指出如果tryd 代码是throws Exception 的方法,我建议的模式将不起作用,但这表明该方法设计不佳。【参考方案7】:

我通常使用相同类型的代码结构,但在三元运算符实际上使代码更好的少数几次之一中将其压缩为一行:

try 
  // code that can throw

catch (Exception e) 
  throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);

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

【讨论】:

【参考方案8】:

lombok 用一个简单的方法注释处理了这个问题?

例子:

import lombok.SneakyThrows;

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

在示例中,该方法应该声明 throws FileNotFoundException,但使用 @SneakyThrows 注释,它没有。

在幕后实际发生的事情是,lombok 对同一问题采取了与 high rated answer 相同的技巧。

任务完成!

【讨论】:

以上是关于如何包装检查的异常但在 Java 中保留原始运行时异常的主要内容,如果未能解决你的问题,请参考以下文章

Python:异常装饰器。如何保留堆栈跟踪

Guava 缓存和保留检查的异常

#yyds干货盘点# Java检查异常和非检查异常,运行时异常和非运行时异常的区别

如何使用命令行检查Java类的异常终止

Java中的异常处理

@Sneaky Throws 在龙目岛的应用