【问题标题】:Can throwing Exception be a good way to handle all of the exceptions that are thrown in Java's reflection API?抛出 Exception 能否成为处理 Java 反射 API 中抛出的所有异常的好方法?
【发布时间】:2010-08-19 12:30:38
【问题描述】:

我发现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);

    }
}

这是不好的做法吗?

【问题讨论】:

    标签: java reflection exception-handling


    【解决方案1】:

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

    专门为此使用java.lang.Exception 是个坏主意,因为它太不具体了。

    您可以将每个不同的异常重新抛出为一些自定义的MyException 或类似的东西。当您对 someMethod 失败的原因并不真正感兴趣时,这有时是一种合理的方法。

    【讨论】:

      【解决方案2】:

      简答

      • 反射在设计上是冗长的
      • 异常翻译是惯用的(捕获较低级别的异常并将其抽象到较高级别)
        • ...但不要过度使用!
      • 首选自定义异常而不是java.lang.Exception;这可能太抽象程度太高了

      第 1 点:尽可能避免反思

      这里引用Effective Java 2nd Edition,Item 53:Prefer interfaces to reflection:

      然而,[反思的力量]是有代价的:

      • 您将失去编译时检查的所有好处,包括异常检查。
      • 执行反射访问所需的代码既笨拙又冗长。写作乏味,阅读困难。
      • 性能下降。反射方法调用比普通方法调用慢得多。

      [...] 通常,在运行时的正常应用程序中不应反射地访问对象。有一些复杂的应用程序需要反射。例子包括[故意省略]。如果您对自己的应用程序是否属于这些类别之一有任何疑问,它可能不属于。


      第2点:异常翻译可以是个好东西,但不要过度使用

      这是来自Effective Java 2nd Edition,Item 61: Throw exceptions appropriate to the abstraction 的引述。

      当一个方法抛出一个与它执行的任务没有明显联系的异常时,这是令人不安的。当方法传播由较低级别抽象引发的异常时,通常会发生这种情况。 [...]

      为了避免这个问题,较高层应该捕获较低级别的异常,并在其位置抛出异常,这些异常可以用较高级别的抽象来解释。这个成语被称为异常翻译。

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

      如果无法防止来自较低层的异常,则下一个最好的办法是让较高层静默处理这些异常,从而使较高层方法的调用者免受较低层问题的影响。在这些情况下,使用一些适当的日志记录工具来记录异常可能是合适的。这允许管理员调查问题,同时将客户端代码和最终用户与它隔离开来。


      第 3 点:java.lang.Exception 太笼统了

      查看the documentation,可以看到一长串直接已知的子类,主题范围广泛,从反射、RMI、XML 解析、I/O、GUI、密码学等。

      声明方法 throws Exception 可能是一个糟糕的 API 设计决策,因为它不会立即告诉用户任何有用的信息,即异常可能是什么类别,或者在什么情况下它们可能是抛出。将此与假设的ReflectionException 进行对比;这提供了更多信息,它还允许用户专门捕获ReflectionException,而不是例如以某种方式漏掉的IOException。


      相关问题

      【讨论】:

      • 我实际上对第 1 点以及反射的模式替换感兴趣。你能提供任何链接或信息吗?谢谢。
      • @hal10001:您可以提出另一个问题,了解有关// do a bunch of reflection... 的更多信息,看看是否有更好的选择。 EJ2 引用:“反射地创建实例并通过它们的接口或超类正常访问它们”。
      【解决方案3】:

      我同意你的方法,除了我认为抛出异常不好,最终你的应用程序中的每个方法声明都会以“抛出异常”结束。对于这样的情况,只有在您犯了错误时才会发生异常,最好将包装异常设置为未经检查的异常。

      如果方法抛出的异常(包含在 InvocationTargetException 中)可能会让您感兴趣,您可以将其封装为未检查异常的不同子类,或为其创建特定的已检查异常子类,但将其封装起来就像未经检查的异常中的 ClassCastException。

      【讨论】:

      • 谢谢!我正在考虑类似的事情,就像 jwachter 建议的那样。我基本上会抛出一个自定义异常,或者抛出一个运行时异常,这取决于捕获的异常类型。
      【解决方案4】:

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

      1. 我可以从下一个即时级别的错误中恢复吗?如果是这样,请使用所有不同的例外情况。
      2. 我可以从更高级别的错误中恢复吗?如果是这样,请使用包装器异常,因为它会污染所有使用显式技术细节的方法。
      3. 我无法从错误中恢复,或者它发生在完全不同的地方/上面的许多级别。如果将每个异常包装到 RuntimeException 或自定义 ReflectionFailedRuntimeException 中,以避免在整个应用程序中泄漏技术异常。

      此外,通常您应该以一种您可以期望反射在大多数情况下没有异常的情况下工作的方式对其进行编码,因此无论如何将其设为 RuntimeException 包装也是相当可观的。

      【讨论】:

      • 谢谢!虽然我可以看到使用扩展 RuntimeException 的自定义异常的好处,但我无法想象我能够从反射操作中恢复的场景,除非它是一个内部进程。
      • @hal10001:为什么?向用户显示“抱歉,我们目前无法满足此请求,您要...”的注释可能是解决此类问题的合理解决方案(取决于应用程序)。
      • @hal10001:没错。这就是为什么您通常只是将包装放入 RuntimeException 中,因为您无法以有意义的方式恢复。您只需要确保异常向用户提供有用的消息和/或记录它以及它是如何发生的。 @Joachim Sauer:在对话框中显示一条消息以掩盖真正的错误并没有恢复:) 例如,恢复是为了解决问题并向用户提供结果而不显示甚至发生了错误.
      • 您应该期望反射以多种方式失败,并且您应该适当地处理它。更好的当然是避免使用反射。
      【解决方案5】:

      想想你想要实现什么。你的方法的调用者不应该知道你正在使用反射来实现你的目标。那不关它的事。如果您只是将所有这些异常放在您的 throws 子句中,这会破坏该封装。

      因此,捕获这些异常并将它们包装在您自己的中是可行的方法。然后你只需要决定抛出哪个异常。 Exception 本身是一个非常糟糕的主意(我认为它甚至应该是抽象的建议)异常的类型应该与问题有关。

      抛出与您正在尝试执行的操作以及失败原因相关的异常(如果由于给定参数不合适而失败,IllegalArgumentException 将是一个)。还要就是否要抛出已检查或未检查的异常做出明智的决定 (RuntimeException)。

      【讨论】:

        【解决方案6】:

        在某些情况下我会。

        我决定的正常方法是查看调用 someMethod() 的方法会期望或合理地期望知道哪些方法。因此,如果 someMethod() 正在做一些与 IO 相关的事情,我会抛出一个 IOException,如果它与我正在编写的库相关,我可能会抛出一个自定义异常..

        如果 someMethod() 的调用者可以合理地知道它在实现中使用了反射,我只会抛出个别异常。

        我猜关键问题是“如果 someMethod() 没有使用反射,出现了问题,它会做什么?”这通常会给你答案。

        【讨论】:

          【解决方案7】:

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

          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;
          
          }
          }
          

          通常捕获异常应该是为了以某种方式处理它们。简单地重新抛出更大范围的异常对我来说在任何地方都没有意义。

          【讨论】:

            猜你喜欢
            • 2016-04-03
            • 2011-02-04
            • 2013-10-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-03-23
            • 2012-07-07
            相关资源
            最近更新 更多