为什么Exception.fillInStackTrace返回Throwable?

时间:2010-01-08 05:11:36

标签: java exception-handling throwable

我认为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无法捕获第一个函数new Throwable()中的f()
  2. exception handler可以捕获第二个函数e.fillInstackTrace()中的g()
  3. 但第二个函数g()仍然需要throws Throwable。这真的很奇怪,因为我们可以抓住e.fillInstackTrace()
  4. 所以我的问题是为什么Exception.fillInStackTrace不会返回Exception或Exception派生而不是开发这么奇怪的语法?

    修改
    澄清我的问题:我的意思是“奇怪的语法”

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

6 个答案:

答案 0 :(得分:12)

我对你的问题感到困惑。显然有一些你不理解的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的评论:

  

但我抛出的东西抛出e.fillInStackTrace();应该是一个可投掷的内容,而不是例外!

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

  

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

我已经解释了它的原因 - 因为Throwable实际上是原始的Exception。有什么关于我的解释,你不理解或只是说你不喜欢Java的定义方式?

编辑2

  

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

你误解了我在说什么......如果你DID抛出异常,那么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设计人员来说,试图解决这个问题并不值得“破坏他们的勇气”。特别是因为它最多只是一个小小的不便......最多。

答案 1 :(得分:6)

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

你问道:    2.由于设计接收异常处理程序的异常处理程序可以处理Throwable异常,因此不需要标记方法g()抛出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子句没有捕获抛出。

catch子句在g()方法中起作用的原因是当你调用throw e.fillInStackTrace()时,对fillInStackTrace的调用实际上会返回一个Exception(因为e本身就是一个Exception)。由于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将在运行时根据你是抛出异常还是Throwable来确定。

答案 2 :(得分:4)

我认为你必须问Frank Yellin(@author的{​​{1}})。正如其他人所说,java.lang.ExceptionfillInStackTrace()中声明并记录为返回Throwable,因此其返回类型必须为this。它只是由Throwable继承。弗兰克可以在Exception中覆盖它,就像这样:

Exception

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

但我的猜测是,如果存在上述覆盖,那么您就会问为什么public class Exception extends Throwable{ /**Narrows the type of the overridden method*/ @Override public synchronized Exception fillInStackTrace() { super.fillInStackTrace(); return this; } } 没有类似的覆盖,为什么RuntimeException不会覆盖该覆盖,依此类推。所以弗兰克和朋友们可能只是不想写出数百个相同的覆盖。

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

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

Throwable

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

fillInStackTrace()Throwable定义,而不是Exception

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

答案 5 :(得分:2)

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

e.fillInStackTrace == e永远是真的

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

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

或使用演员

    throw (Exception) e.fillInStackTrace();

BTW,initCause()也是如此。