关于java:为什么在某些情况下不声明而重新抛出Throwable是合法的?

Why is it legal to re-throw a Throwable in certain cases, without declaring it?

我希望以下代码在throw t;上引发编译时错误,因为未声明main抛出Throwable,但是它成功编译(在Java 1.7.0_45中),并产生了您期望的输出 如果该编译时错误是固定的,则为0。

1
2
3
4
5
6
7
8
9
10
11
public class Test {
    public static void main(String[] args) {
        try {
            throw new NullPointerException();

        } catch(Throwable t) {
            System.out.println("Caught"+t);
            throw t;
        }
    }
}

如果将Throwable更改为Exception,它也会进行编译。

不会按预期方式编译:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
    public static void main(String[] args) {
        try {
            throw new NullPointerException();

        } catch(Throwable t) {
            Throwable t2 = t;
            System.out.println("Caught"+t2);
            throw t2;
        }
    }
}

这样编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
    public static void main(String[] args) {
        try {
            throwsRuntimeException();

        } catch(Throwable t) {
            System.out.println("Caught"+t);
            throw t;
        }
    }

    public static void throwsRuntimeException() {
        throw new NullPointerException();
    }
}

这不是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
    public static void main(String[] args) {
        try {
            throwsCheckedException();

        } catch(Throwable t) {
            System.out.println("Caught"+t);
            throw t;
        }
    }

    public static void throwsCheckedException() {
        throw new java.io.IOException();
    }
}

这也可以编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
    public static void main(String[] args) throws java.io.IOException {
        try {
            throwsIOException();

        } catch(Throwable t) {
            System.out.println("Caught"+t);
            throw t;
        }
    }

    public static void throwsIOException() throws java.io.IOException {
        throw new java.io.IOException();
    }
}

一个更复杂的示例-被检查的异常由外部catch块捕获,而不是被声明为抛出。 这样编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
    public static void main(String[] args) {
        try {
            try {
                throwsIOException();

            } catch(Throwable t) {
                System.out.println("Caught"+t);
                throw t;
            }
        } catch(java.io.IOException e) {
            System.out.println("Caught IOException (outer block)");
        }
    }

    public static void throwsIOException() throws java.io.IOException {
        throw new java.io.IOException();
    }
}

因此,当编译器可以确定捕获的异常始终合法地重新抛出时,似乎有一种特殊情况允许重新抛出异常。 它是否正确? JLS在哪里指定? 还有其他类似的难解之类的案例吗?


JLS 11.2.2(重点是我)对此进行了介绍:

A throw statement whose thrown expression is a final or effectively final exception parameter of a catch clause C can throw an exception class E iff:

  • E is an exception class that the try block of the try statement which declares C can throw; and

  • E is assignment compatible with any of C's catchable exception classes; and

(...)

换句话说,文档中引用的类型E是可以引发的类型,而不是捕获它的catch子句参数的类型(可捕获的异常类)。它只需要在分配上与catch子句参数兼容,但是在分析中不使用该参数的类型。

这就是为什么要竭尽全力说出最终或有效的最终异常参数的原因-如果重新分配了示例中的t,则分析将无法进行。


因为编译器足够聪明,所以知道不能从try块中抛出已检查的异常,因此捕获的Throwable不是必须声明的已检查的异常。

请注意,如果我没有记错的话,从Java 7开始就是如此。


当捕获ThrowableException并且变量实际上是final时,您可以重新抛出相同的变量,编译器将知道您可能在try {} catch块中抛出了哪些已检查的异常。