抛出异常是否是处理Java反射API中抛出的所有异常的好方法?

4

我发现Java的反射API异常冗长,我经常想捕获每个特定的异常,但只是抛出异常。这是不好的实践吗?或者你真的在方法签名中抛出所有这些异常吗?然后调用该方法的每个方法都必须以某种方式处理这些特定的异常。相反,我考虑这样做:

public void someMethod()
        throws Exception {

    try {

        // do a bunch of reflection...

    } catch(ClassCastException classCastException) {

        throw new Exception("Some specific message.", classCastException);

    } catch(InvocationTargetException invocationTargetException) {

        throw new Exception("Some specific message.", invocationTargetException);

    }
}

这是不良惯例吗?
7个回答

9

有时,将一个方法抛出的不同类型的异常重新抛出为单一的统一异常可能是一个合理的方法。

使用java.lang.Exception来实现这一点是一个糟糕的想法,因为它太过于不具体。

您可以将每个不同的异常重新抛出为一些自定义的MyException或类似的异常。当您真正不关心someMethod失败的原因时,这通常是一个合理的方法。


6

简短回答

  • 反射设计上就是冗长的。
  • 异常翻译是惯用法(捕获低级别异常并将其抽象为更高级别)。
    • ... 但不要过度使用!
  • 优先使用自定义异常而不是java.lang.Exception; 它可能是一个过于高级的抽象。

要点1:尽可能避免使用反射

以下是来自《Effective Java 2nd Edition》第53条“优先考虑使用接口而非反射”的引用:

然而,[反射的强大]是有代价的:

  • 您失去了所有编译时检查的好处,包括异常检查。
  • 执行反射访问所需的代码笨拙且冗长。编写起来很繁琐,阅读起来很困难。
  • 性能受到影响。反射方法调用比普通方法调用慢得多。

[...] 通常情况下,在正常应用程序运行时不应反射访问对象。有一些复杂的应用程序需要使用反射。例如 [故意省略]。如果您对应用程序是否属于这些类别有任何疑问,那么它可能不属于这些类别。


重点二:异常翻译可以是一件好事,但不要过度使用

以下是来自《Effective Java 2nd Edition》第61条:抛出适合抽象层次的异常的引用。

当一个方法抛出与其执行任务没有明显关联的异常时,这会让人感到不安。这种情况经常发生在一个方法传播了由较低层次抽象抛出的异常时。[...]

为了避免这个问题,高层应该捕获低层的异常并抛出可用更高层次抽象解释的异常。这种惯用法被称为异常翻译

[...] 虽然异常翻译优于毫无头绪地从低层次传播异常,但不应过度使用。在可能的情况下,处理来自低层次的异常的最佳方法是通过确保低层次方法成功来避免它们。[...]

如果无法防止来自低层的异常,则下一个最好的方法是使高层在这些异常周围默默地工作,隔离高层方法的调用者与低层次问题。在这种情况下,可以使用一些适当的日志记录工具记录异常。这使管理员可以调查问题,同时隔离客户端代码和最终用户。


第三点:java.lang.Exception过于笼统

查看文档,可以看到一长串直接已知的子类,涵盖了广泛的主题,包括反射、RMI、XML解析、I/O、GUI、加密等。

声明一个方法throws Exception可能是一个糟糕的API设计决策,因为它不能立即告诉用户关于异常可能属于哪个类别或在什么情况下会抛出。相比之下,假设有一个ReflectionException,这更具信息量,并且还允许用户专门捕获ReflectionException而不是像IOException这样的异常被忽略。


相关问题


我实际上对第一点和反射的模式替换很感兴趣。你能提供任何链接或信息吗?谢谢。 - user4903
@hal10001:您可以提出另一个问题,更详细地说明// do a bunch of reflection...,看看是否有更好的替代方案。EJ2引用:“通过反射创建实例,并通过它们的接口或超类正常访问它们”。 - polygenelubricants

2

我同意你的方法,不过我认为抛出异常并不好,最终你的应用程序中每个方法声明都会以“throws Exception”结束。对于像这样的情况,如果异常只会在你犯错误时发生,那么将包装异常作为未检查的异常更好。

如果方法抛出的异常(包含在InvocationTargetException中)可能对你有趣,你可以将其包装成不同的未检查异常子类或创建一个特定的已检查异常子类,但是将ClassCastException等异常包装在未检查异常中。


谢谢!我正在考虑类似的东西,就像jwachter建议的那样。基本上,我会抛出一个自定义异常,或者抛出运行时异常,具体取决于捕获到的异常类型。 - user4903

1

使用throws Exception总是一个不好的主意。在您的情况下,您必须考虑以下事项。

  1. 我能否在下一个立即级别上从错误中恢复?如果可以,请使用所有不同的异常以获得清晰度。
  2. 我能否从几个级别上的错误中恢复?如果可以,请使用包装器异常,因为它会污染所有方法,并使用显式的技术细节。
  3. 我无法从错误中恢复,或者它发生在完全不同的地方/许多级别以上。如果将每个异常包装成RuntimeException或自定义的ReflectionFailedRuntimeException,以避免技术异常泄漏到应用程序中。

此外,通常您应该编写代码,以便可以预期反射在大多数情况下都能正常工作,而不需要抛出异常,因此无论如何都应该进行RuntimeException包装。


谢谢!虽然我能看到使用扩展RuntimeException的自定义异常的好处,但是除非这是内部过程,否则我无法想象出能从反射操作中恢复的情况。 - user4903
@hal10001:为什么?向用户显示一个提示,上面写着“抱歉,我们目前无法满足此请求,您是否想要...”可能是解决这个问题的合理方案(取决于应用程序)。 - Joachim Sauer
@hal10001: 没错。这就是为什么通常只需将其包装到_RuntimeException_中,因为您无法以有意义的方式恢复。您只需要确保异常向用户提供有用的消息和/或记录它发生的方式。@Joachim Sauer: 在对话框中显示掩盖真实错误的消息并不是恢复 :) 恢复的例子是解决问题并向用户提供结果,而不显示发生错误。 - Johannes Wachter
你应该预料到反射会以各种方式失败,并适当地处理它。当然,更好的方法是避免使用反射。 - Tom Hawtin - tackline

1

先想好你想要实现什么。调用你的方法的人不应该知道你正在使用反射来实现你的目标,这与他们无关。如果你只是把所有这些异常放在你的 throws 子句中,那就会破坏封装性。

因此,捕获这些异常并将它们包装成自己的异常是正确的方法。然后你只需要决定要抛出哪个异常。Exception 本身是一个非常糟糕的想法(我认为它甚至应该是抽象的,以表明这一点)。异常的类型应该与问题相关。

抛出与你尝试做的事情以及失败的原因相关的异常(如果失败是因为给定的参数不合适,则 IllegalArgumentException 就是其中之一)。还要根据情况决定是否要抛出已检查或未检查的异常(RuntimeException)。


0

如果您正在收集所有异常并抛出自定义异常,则仍然是一种可接受的策略。

public void someMethod()
    throws MyCustomException {

try {

    // do a bunch of reflection...

} catch(ClassCastException classCastException) {
    MyCustomException ex1 = new MyCustomException("Some specific message.", classCastException);
// Logic to add more details to exception AND/OR handling current exception.
    throw ex1;

} catch(InvocationTargetException invocationTargetException) {

            MyCustomException ex1 = new MyCustomException("Some specific message.", classCastException);
// Logic to add more details to exception AND/OR handling current exception.
    throw ex1;

}
}

通常捕获异常应该是为了以某种方式处理它们的原因。对我来说,简单地重新抛出更大范围的异常毫无意义。

0

在某些情况下,我会这样做。

我通常的决定方式是查看调用 someMethod() 方法的方法需要或者可以合理地知道什么。所以如果 someMethod() 做了一些 IO 相关的事情,我会抛出一个 IOException,如果它与我正在编写的库有关,我可能会抛出一个自定义异常。

只有当 someMethod() 的调用者可以合理地知道它使用反射来实现时,我才会抛出单独的异常。

我想关键问题是:“如果 someMethod() 没有使用反射,发生了什么错误,它会怎么做?”这通常会给你答案。


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