5

我认为 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().

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

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

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

谢谢。

4

6 回答 6

12

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.

于 2010-01-08T06:33:04.493 回答
6

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.

于 2010-01-14T19:00:02.963 回答
4

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.

于 2010-01-13T14:20:07.670 回答
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
于 2010-01-08T05:18:24.983 回答
2

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

于 2010-01-08T05:23:37.703 回答
2

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().

于 2010-01-11T09:51:16.660 回答