为什么 Exception.fillInStackTrace 返回 Throwable?

发布于 2024-08-17 07:48:51 字数 1509 浏览 10 评论 0原文

我认为 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. 异常处理程序无法捕获第一个函数f()中的new Throwable()
  2. 异常处理程序可以捕获第二个函数g()中的e.fillInstackTrace()
  3. 但第二个函数 g() 仍然需要抛出 Throwable。这真的很奇怪,因为我们可以捕获e.fillInstackTrace()

所以我的问题是为什么 Exception.fillInStackTrace 不返回异常或异常派生而不是开发如此奇怪的语法?

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

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

谢谢。

I think Exception.fillInStackTrace should return Exception or derived Exception objects. Considering the two functions below,

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. The exception handler could not catch the new Throwable() in the first function f().
  2. The exception handler could catch the e.fillInstackTrace() in the second function g().
  3. But the second function g() would still need to throws Throwable. This is really strange, since we could catch e.fillInstackTrace().

So my question is why doesn't Exception.fillInStackTrace return Exception or Exception-derived instead of developing such a strange syntax?

EDIT:
To clarify my question: What I mean by "strange syntax" are

  1. Since Exception.fillInStackTrace() return Throwable reference,the exception handler which recieve Exception reference should not be able to catch the exception.Because java does not allow implict downcast,it should be something like return (Exception)e.fillInstackTrace().
  2. Since it is designed that the exception handler recieving Exception reference could handle the Throwable exception,there is no need to mark the method g() throws Throwable exception.But java compiler would enforce us to do so.

thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

止于盛夏 2024-08-24 07:48:51

我对你的问题比较困惑。显然有一些关于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的评论:

但是我用 throw e.fillInStackTrace(); 抛出了什么;应该是 Throwable 实例,而不是异常!

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

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

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

编辑2

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

您误解了我的意思...也就是说,如果您确实抛出了 Exception,那么 会抛出 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 设计者“绞尽脑汁”试图解决这个问题根本不值得。特别是因为这实际上只是一个小小的不便......最多。

I'm rather puzzled by your question. There's clearly something about java exceptions / exception handling that you don't understand. So lets start at the beginning.

In java, all exceptions (in the sense that this term is used in the Java Language Specification) are instances of a some class that is a subclass of java.lang.Throwable. There are two (and only two) direct subclasses of Throwable; i.e. java.lang.Exception and java.lang.Error. Instances of all of these classes ... including instances of Throwable and Error ... are referred to as exceptions in the JLS.

An exception handler catches exceptions (in the JLS sense) that are assignment compatible with the exception type used in the catch declaration. So for example:

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

will catch any exception thrown in the try block that is an instance of java.lang.Exception or of a direct or indirect subtype of java.lang.Exception. But it won't catch an instance of java.lang.Throwable, because that is (obviously) not one of the above.

On the other hand:

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

will catch an instance of java.lang.Throwable.

Reviewing your example in the light of this, it is obvious why the f method is not catching the Throwable instance: it doesn't match the exception type in the catch clause! By contrast, in the g method the Exception instance matched the exception type in the catch clause and is therefore caught.

I don't understand what you are saying about needing to throw a Throwable in g. Firstly, the fact that the method declares that it throws Throwable does not mean that it actually needs to throw it. All it is saying is that it might throw something assignable to Throwable ... possibly in some future version of the g method. Secondly, if you were to add throw e; to the outer catch block, it would be throwing something that is assignable to Throwable.

Finally, it is generally a bad idea to be creating instances of Throwable, Exception, Error and RuntimeException. And you need to be very careful when and how you catch them. For example:

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

EDIT - in response OP's comments:

But what I throw with throw e.fillInStackTrace(); should be an Intance of Throwable,not Exception!

The Javadoc says specifically that the object returned is the exception object you are calling the method on. The purpose of the fillInStacktrace() method is to fill in the stack trace for an existing object. If you want a different exception, you should use new to create one.

Actually,I mean the outter exception handler should not catch the Throwable thrown by throw e.fillInStackTrace().

I have explained why it does - because the Throwable is actually the original Exception. Is there something about my explanation that you do not understand or are you simply saying that you don't like the way that Java is defined?

EDIT 2

And if the outter exception handler could handle the Throwable exception,why must we specify that the method g would throw Throwable exception

You misunderstand what I was saying ... which was that if you DID throw an Exception, then the throws Throwable would not be redundant. OTOH, I finally think I understand your complaint.

I think that the crux of your complaint is that you'd get a compilation error with this:

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

I can see that this is somewhat unexpected. But it is unavoidable I'm afraid. There is NO way (short of a major change to Java's type system) that you could declare the signature of the fillInStacktrace that will work in all cases. For example, if you moved the declaration of the method to the Exception class, you'd just repeat the same problem with subtypes of Exception. But if you tried to express the signature using a generic type parameter, it would entail making all subclasses of Throwable explicit generic types.

Fortunately, the cure is really simple; cast the result of fillInStacktrace() as follows:

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

And the final point is that it is very unusual for an application to explicitly call fillInStacktrace(). In the light of that, it would simply have not been worthwhile for the Java designers to have "busted their guts" trying to solve this. Especially since it is really only a minor inconvenience ... at most.

时光瘦了 2024-08-24 07:48:51

实际上从问题 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 的调用实际上返回一个 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),它将在运行时确定是调用第一个还是第二个 process 方法。同样,catch(Exception e) 子句是否可以捕获您的 throw 将在运行时根据您抛出的是 Exception 还是 Throwable 来确定。

It's actually easier to answer your questions starting with question 2.

You asked:
2. Since it is designed that the exception handler recieving Exception reference could handle the Throwable exception,there is no need to mark the method g() throws Throwable exception.But java compiler would enforce us to do so.

Answer:
Actually, catch( Exception e) cannot catch a Throwable.
Try this:

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

You'll see that the catch clause does not catch the throw in this case.

The reason that the catch clause works in your g() method is that when you invoke throw e.fillInStackTrace(), the call to fillInStackTrace actually returns an Exception (that's because e is an Exception itself). Since Exception is a subclass of Throwable, that does not contradict the declaration of fillInStackTrace.

Now on to the first question

You asked:
1. Since Exception.fillInStackTrace() return Throwable reference,the exception handler which recieve Exception reference should not be able to catch the exception.Because java does not allow implict downcast,it should be something like return (Exception)e.fillInstackTrace().

Answer:
This is not exactly an implicit downcast. Think of this as a variation of overloading.

Let's say you have

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

If you call process(someObject), it will be determined at runtime whether the first or the second process method gets called. Similarly, whether or not the catch(Exception e) clause can catch your throw will be determined at runtime, based on whether you throw an Exception or Throwable.

素染倾城色 2024-08-24 07:48:51

我认为您必须询问 Frank Yellin(java.lang.Exception@author)。正如其他人所说, 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 及更高版本中,允许使用协变返回类型,并且上述内容是合法的。

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

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

使用泛型,人们可以想象将 Throwable 的声明增强为:

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

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

I think you'd have to ask Frank Yellin (the @author of java.lang.Exception). As others have said, fillInStackTrace() is declared in Throwable and documented to return this, so its return type must be Throwable. It's just inherited by Exception. Frank could have overridden it in Exception, like this:

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

... but he didn't. Before Java 1.5, the reason is that it would have produced a compilation error. In 1.5 and beyond, covariant return types are allowed and the above is legal.

But my guess is that if the above override existed, you'd then be asking why RuntimeException doesn't have a similar override, why ArrayStoreException doesn't override that override, and so on. So Frank and friends probably just didn't want to write those hundreds of identical overrides.

It's worth noting that, if you're dealing with your own custom exception classes, you can easily override the fillinStackTrace() method as I've done above, and get the narrower type that you're after.

Using generics, one could imagine augmenting the declaration of Throwable to:

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

... but that's not really satisfactory, for reasons that are beyond the scope of this answer.

著墨染雨君画夕 2024-08-24 07:48:51

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

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 returns the reference to the same object. It's method chaining to allow re-throwing the Exception and resetting exception's stack trace.

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

The method return type can only be base type which is Throwable. It can be generified so that the method returns the parametrized type. But that's not the case for now.

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
冷血 2024-08-24 07:48:51

fillInStackTrace()Throwable 定义,而不是 Exception

并非所有 Throwable 的子类都是异常(例如 Error)。就 Throwable 而言,它唯一能保证 fillInStackTrace() 的返回值的是它是 Throwable 的实例(因为它只返回相同的对象,正如 Chandra Patni 指出的那样)。

fillInStackTrace() is defined by Throwable, not Exception.

Not all subclasses of Throwable are exceptions (Error, for example). As far as Throwable is concerned, the only thing it can guarantee about the return value of fillInStackTrace() is that it's an instance of Throwable (since it just returns the same object, as Chandra Patni noted).

花想c 2024-08-24 07:48:51

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

e.fillInStackTrace == e 始终为 true

这只是一个快捷方式,您也可以编写

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

或使用强制转换

    throw (Exception) e.fillInStackTrace();

BTW,initCause() 也会发生同样的情况

Actually fillInStackTrace returns the same Object it was invoked on.

e.fillInStackTrace == e is always true

It is just a shortcut, you can also just write

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

or use a cast

    throw (Exception) e.fillInStackTrace();

BTW, the same happens with initCause().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文