【问题标题】:Why does Exception.fillInStackTrace return Throwable?为什么 Exception.fillInStackTrace 返回 Throwable?
【发布时间】:2012-08-14 13:16:40
【问题描述】:

我认为 Exception.fillInStackTrace 应该返回 Exception 或派生的 Exception 对象。考虑以下两个函数,

public static void f() throws Throwable {
    try {
        throw new Throwable();
    } catch (Exception e) {
        System.out.println("catch exception e");
        e.printStackTrace();
    } 
}
public static void g() throws Throwable {
    try {
        try {
            throw new Exception("exception");
        } catch (Exception e) {
            System.out.println("inner exception handler");
            throw e.fillInStackTrace();
        }
    } catch (Exception e) {
        System.out.println("outer exception handler");
        e.printStackTrace();
    }
}
  1. exception handler 无法在第一个函数f() 中捕获new Throwable()
  2. exception handler 可以在第二个函数g() 中捕获e.fillInstackTrace()
  3. 但是第二个函数g() 仍然需要throws Throwable。这真的很奇怪,因为我们可以捕捉到e.fillInstackTrace()

所以我的问题是为什么 Exception.fillInStackTrace 不返回 Exception 或 Exception-derived 而不是开发这种奇怪的语法?

编辑
澄清我的问题:我所说的“奇怪的语法”是什么意思

  1. 由于Exception.fillInStackTrace()返回Throwable引用,接收Exception引用的异常处理程序应该无法捕获异常。因为java不允许隐式向下转换,它应该类似于return (Exception)e.fillInstackTrace()。李>
  2. 由于设计为接收Exception引用的异常处理程序可以处理Throwable异常,因此无需标记方法g()抛出Throwable异常。但是java编译器会强制我们这样做.

谢谢。

【问题讨论】:

    标签: java exception-handling throwable


    【解决方案1】:

    我对你的问题感到很困惑。显然,您不了解有关 java 异常/异常处理的某些内容。所以让我们从头开始。

    在 java 中,所有异常(在 Java 语言规范中使用该术语的意义上)都是某个类的实例,该类是java.lang.Throwable 的子类。 Throwable 有两个(也只有两个)直接子类;即java.lang.Exceptionjava.lang.Error。所有这些类的实例……包括 Throwable 和 Error 的实例……在 JLS 中被称为异常。

    异常处理程序捕获与catch 声明中使用的异常类型兼容的异常(在 JLS 意义上)。比如:

    try {
        ....
    } catch (Exception ex) {
        ...
    }
    

    将捕获try 块中抛出的任何异常,该块是java.lang.Exception 的实例或java.lang.Exception 的直接或间接子类型。但它不会捕获java.lang.Throwable 的实例,因为(显然)不是上述之一。

    另一方面:

    try {
        ....
    } catch (Throwable ex) {
        ...
    }
    

    捕获java.lang.Throwable的实例。

    鉴于此查看您的示例,很明显为什么 f 方法没有捕获 Throwable 实例:它与 catch 子句中的异常类型不匹配!相比之下,g 方法中的 Exception 实例与 catch 子句中的异常类型匹配,因此被捕获。

    我不明白你在说什么需要在g 中抛出一个 Throwable。首先,该方法声明它抛出 Throwable 并不意味着它实际上需要抛出它。它只是说它可能抛出一些可分配给 Throwable 的东西......可能在 g 方法的未来版本中。其次,如果您要将 throw e; 添加到外部 catch 块,它抛出一些可分配给 Throwable 的东西。

    最后,创建 Throwable、Exception、Error 和 RuntimeException 的实例通常是个坏主意。你需要非常小心何时以及如何捕捉它们。例如:

    try {
         // throws an IOException if file is missing
        InputStream is = new FileInputStream("someFile.txt");
        // do other stuff
    } catch (Exception ex) {
        System.err.println("File not found");
        // WRONG!!!  We might have caught some completely unrelated exception;
        // e.g. a NullPointerException, StackOverflowError, 
    }
    

    编辑 - 响应 OP 的 cmets:

    但是我用 throw e.fillInStackTrace();应该是 Throwable 的 Intance,而不是 Exception!

    Javadoc 明确指出返回的对象是您调用该方法的异常对象。 fillInStacktrace() 方法的目的是填充现有对象的堆栈跟踪。如果你想要一个不同的异常,你应该使用new来创建一个。

    实际上,我的意思是外部异常处理程序不应该捕获由 throw e.fillInStackTrace() 抛出的 Throwable。

    我已经解释了为什么会这样 - 因为 Throwable 实际上是原始异常。我的解释有什么你不理解的地方,还是只是说你不喜欢 Java 的定义方式?

    编辑 2

    如果外部异常处理程序可以处理 Throwable 异常,为什么我们必须指定方法 g 会抛出 Throwable 异常

    你误解了我在说什么......也就是说,如果你确实抛出了异常,那么 throws Throwable 就不会是多余的。 OTOH,我终于明白你的抱怨了。

    我认为您投诉的症结在于您会收到以下编译错误:

    public void function() throws Exception {
        try {
            throw new Exception();
        } catch (Exception ex) {
            throw ex.fillInStackTrace();
            // according to the static type checker, the above throws a Throwable
            // which has to be caught, or declared as thrown.  But we "know" that the 
            // exception cannot be anything other than an Exception.
        }
    }
    

    我可以看出这有点出乎意料。但恐怕这是不可避免的。没有办法(除非对 Java 的类型系统进行重大更改)可以声明适用于所有情况的 fillInStacktrace 的签名。例如,如果您将方法的声明移至 Exception 类,您只会对 Exception 的子类型重复同样的问题。但是,如果您尝试使用泛型类型参数来表达签名,则需要将 Throwable 的所有子类都设为显式泛型类型。

    幸运的是,治疗真的很简单;将fillInStacktrace() 的结果转换如下:

    public void function() throws Exception {
        try {
            throw new Exception();
        } catch (Exception ex) {
            throw (Exception) (ex.fillInStackTrace());
        }
    }
    

    最后一点是,应用程序显式调用fillInStacktrace() 是非常不寻常的。鉴于此,Java 设计人员“拼命”试图解决这个问题是不值得的。特别是因为这实际上只是一个小小的不便......最多。

    【讨论】:

    • By contrast, in the g method the Exception instance matched the exception type in the catch clause and is therefore caught. 但是我用throw e.fillInStackTrace(); 抛出的应该是 Throwable 的 Intance,而不是 Exception!
    • 实际上,我的意思是外部异常处理程序不应该捕获throw e.fillInStackTrace()抛出的Throwable
    • 如果外部异常处理程序可以处理Throwable异常,为什么我们必须指定方法g会抛出Throwable异常。
    • 不错的详细答案,值得一票(和奖金;-))
    • 可以创建用户定义的检查异常,它们是 java.lang.Throwable 的子类,但不是 java.lang.Exception。
    【解决方案2】:

    从问题 2 开始回答您的问题实际上更容易。

    你问: 2. 由于设计了接收Exception引用的异常处理器可以处理Throwable异常,所以不需要标记方法g() throws Throwable异常,但是java编译器会强制我们这样做。

    回答: 实际上,catch(Exception e) 无法捕获 Throwable。 试试这个:

    try {
           Throwable t = new Throwable();
           throw t.fillInStackTrace();
    } catch (Exception e) {
        System.out.println("outer exception handler");
        e.printStackTrace();
    } 
    

    您会看到在这种情况下,catch 子句没有捕获 throw。

    catch 子句在您的 g() 方法中起作用的原因是,当您调用 throw e.fillInStackTrace() 时,对 fillInStackTrace 的调用实际上会返回一个异常(这是因为 e 本身就是一个异常)。由于 Exception 是 Throwable 的子类,这与 fillInStackTrace 的声明并不矛盾。

    现在开始第一个问题

    你问: 1. 由于 Exception.fillInStackTrace() 返回 Throwable 引用,因此接收到 Exception 引用的异常处理程序应该无法捕获该异常。由于 java 不允许隐式向下转换,它应该类似于 return (Exception)e.fillInstackTrace() .

    回答: 这并不完全是一种隐含的沮丧。将此视为重载的一种变体。

    假设你有

    void process(Throwable t){
       ...
    }
    void process(Exception e){
      ...
    } 
    

    如果调用process(someObject),将在运行时确定是调用第一个还是第二个处理方法。同样,catch(Exception e) 子句是否可以捕获您的 throw 将在运行时根据您是抛出 Exception 还是 Throwable 来确定。

    【讨论】:

      【解决方案3】:

      我想你得问问 Frank Yellin(@authorjava.lang.Exception)。正如其他人所说,fillInStackTrace()Throwable 中声明并记录为返回this,因此其返回类型必须为Throwable。它只是由Exception 继承。 Frank 可以在 Exception 中覆盖它,如下所示:

      public class Exception extends Throwable{
          /**Narrows the type of the overridden method*/
          @Override
          public synchronized Exception fillInStackTrace() {
              super.fillInStackTrace();
              return this;
          }
      }
      

      ...但他没有。在 Java 1.5 之前,原因是它会产生编译错误。在 1.5 及更高版本中,协变返回类型 are allowed 和以上是合法的。

      但我的猜测是,如果上面的覆盖存在,你会问为什么RuntimeException 没有类似的覆盖,为什么ArrayStoreException 没有覆盖那个覆盖,等等。所以弗兰克和朋友们可能只是不想写那数百个相同的覆盖。

      值得注意的是,如果您正在处理自己的自定义异常类,您可以像我在上面所做的那样轻松覆盖 fillinStackTrace() 方法,并获得您所追求的更窄的类型。

      使用泛型,可以想象将Throwable 的声明扩展为:

      public class Throwable<T extends Throwable<T>>{
          public synchronized native T fillInStackTrace();
      }
      

      ...但这并不令人满意,原因超出了此答案的范围。

      【讨论】:

        【解决方案4】:

        fillInStackTrace 返回对同一对象的引用。它是允许重新抛出 Exception 并重置异常的堆栈跟踪的方法链接。

        public static void m() {
            throw new RuntimeException();
        
        }
        
        public static void main(String[] args) throws Throwable {
            try {
                m();
            } catch (Exception e) {
                e.printStackTrace();
                throw e.fillInStackTrace();
            }
        }
        

        方法返回类型只能是基类型Throwable。可以对其进行泛型化,以便该方法返回参数化类型。但目前情况并非如此。

        RuntimeException e = new RuntimeException();
        Throwable e1 = e.fillInStackTrace();
        System.out.println(e1.getClass().getName()); //prints java.lang.RuntimeException
        System.out.println(e == e1); //prints true
        

        【讨论】:

        • 那么 fillInStackTrace() 有可能返回异常或异常派生的东西吗?
        • 当然。事实上,它将与您调用该方法的类型相同。
        • 事实上,根据 javadoc,它返回“对此 Throwable 实例的引用”。因此,它不仅是相同的类型,而且是相同的对象,正如答案中所述;-)
        • 你说它可以被泛化——但我认为没有任何方法可以声明它,以便它总是返回它被调用的变量的类型,这是有用的。这是 Java 泛型的一个已知缺点。目前你能做的最好的就是一个辅助方法 E fillInStackTrace(E e).
        【解决方案5】:

        fillInStackTrace()Throwable 定义,而不是 Exception

        并非Throwable 的所有子类都是例外(例如Error)。就Throwable 而言,它唯一可以保证fillInStackTrace() 的返回值是它是Throwable 的一个实例(因为它只是返回相同的对象,如Chandra Patni 注明)。

        【讨论】:

          【解决方案6】:

          实际上fillInStackTrace 返回调用它的同一个对象。

          e.fillInStackTrace == e 始终为真

          只是一个快捷方式,你也可以直接写

              } catch (Exception e) {
                  System.out.println("inner exception handler");
                  e.fillInStackTrace();
                  throw e;
              }
          

          或使用演员表

              throw (Exception) e.fillInStackTrace();
          

          顺便说一句,initCause() 也是如此。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多