为什么Exception.fillInStackTrace返回Throwable?

Jic*_*hao 5 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();
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 在exception handler没能赶上new Throwable()第一功能f().
  2. 在exception handler可以赶上e.fillInstackTrace()第二功能g().
  3. 但第二个功能g()仍然需要throws Throwable.这真的很奇怪,因为我们可以抓住e.fillInstackTrace().

所以我的问题是为什么Exception.fillInStackTrace不返回Exception或Exception派生而不是开发这样一个奇怪的语法?

编辑:
要澄清我的问题:我所说的"奇怪的语法"的意思是

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

谢谢.

Ste*_*n C 12

我对你的问题感到困惑.显然有一些你不理解的java异常/异常处理.所以让我们从头开始吧.

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

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

try {
    ....
} catch (Exception ex) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

另一方面:

try {
    ....
} catch (Throwable ex) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

将捕获一个实例java.lang.Throwable.

根据这一点回顾你的例子,很明显为什么该f方法没有捕获Throwable实例:它与catch子句中的异常类型不匹配!相比之下,在g方法中,Exception实例与catch子句中的异常类型匹配,因此被捕获.

我不明白你在说什么需要投掷一个Throwable g.首先,该方法声明它抛出Throwable的事实并不意味着它实际上需要抛出它.所有这一切都说它可能会抛出可分配给Throwable的东西...可能在该g方法的某个未来版本中.其次,如果你要添加throw e;到外catch块,这将是扔东西是分配给抛出.

最后,创建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, 
}
Run Code Online (Sandbox Code Playgroud)

编辑 - 回应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.
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以看出这有些出乎意料.但是我害怕这是不可避免的.没有办法(对Java的类型系统没有重大改变)你可以声明fillInStacktrace在所有情况下都可以使用的签名.例如,如果您将方法的声明移动到Exception类,那么您只需要对Exception的子类型重复相同的问题.但是,如果您尝试使用泛型类型参数表达签名,则需要创建Throwable的所有子类显式泛型类型.

幸运的是,治愈非常简单; 施放结果fillInStacktrace()如下:

public void function() throws Exception {
    try {
        throw new Exception();
    } catch (Exception ex) {
        throw (Exception) (ex.fillInStackTrace());
    }
}
Run Code Online (Sandbox Code Playgroud)

最后一点是应用程序显式调用是非常不寻常的fillInStacktrace().鉴于此,对于Java设计人员来说,试图解决这个问题并不值得"破坏他们的勇气".特别是因为它真的只是一个小小的不便......最多.


Ole*_*boy 6

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

您问:2.因为设计接收Exception引用的异常处理程序可以处理Throwable异常,所以不需要标记方法g()抛出Throwable异常.但是java编译器会强制我们这样做.

答:实际上,catch(例外e)无法捕获Throwable.试试这个:

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

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

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){
  ...
} 
Run Code Online (Sandbox Code Playgroud)

如果调用process(someObject),将在运行时确定是调用第一个还是第二个进程方法.类似地,catch(Exception e)子句是否可以捕获你的throw将在运行时根据你是抛出Exception还是Throwable来确定.