让我们采用您的代码的第一个版本:
public class Exption<T extends Exception> {
public static void main(String[] args) {
try {
new Exption<RuntimeException>().pleaseThrow(new SQLException());
}catch (final SQLException ex){ // This is compilation error
ex.printStackTrace();
}
}
private void pleaseThrow(final Exception t) throws T{
throw (T)t;
}
}
编译器如何检测错误?
不是因为throw (T)t;。事实上,如果您将类型转换为类型参数,编译器会忽略它并将其留给 JVM。
那么编译器是如何产生错误的呢?
正因为如此:
private void pleaseThrow(final Exception t) throws T
注意 throws T 。当您说 new Exption<RuntimeException>() 时,编译器不会创建任何对象。但它在编译期间推断T。它在它的权力之下。
现在,让我们全面了解为什么会产生错误。
private void pleaseThrow(final Exception t) throws T{ 的编译器知道您将抛出 RuntimeException。
根据类型转换的规则,编译器检查两种类型,如果一个是另一个的父级。如果是,它会将代码传递给 JVM。然后只有 JVM 会进一步检查一个对象是否可以实际类型转换为另一个对象。
同样,编译器检查 throws 和 catch 块是否兼容。如果有多个兼容,则将决定权留给 JVM。编译器也会检查许多其他事情,但让我们专注于主轨道。
在您的示例中,一种类型是 SqlException,另一种是 RuntimeException。没有人是其他人的父母。因此,编译器显示错误。
让我们看几个例子来清除它:
public class Exption<T extends Exception> {
public static void main(String[] args) {
try {
new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
}catch (final ClassCastException ex){
ex.printStackTrace();
System.out.println("done");
}
}
private void pleaseThrow(final Exception t) throws T{
throw (T)t;
}
}
在这个例子中,Catch 子句不会被调用,但是代码编译得很好。
由于 ClassCastException 是 RuntimeException ,因此编译器可以很好地编译代码。 RuntimeException 是 ClassCastException 的父级。
但是当编译器将代码交给 JVM 时,JVM 知道异常对象的类型为 IllegalArgumentException,因此将满足于 IllegalArgumentException 的 catch 子句或其超类型。在这里,我们没有这样的东西。因此,不会调用 Catch 子句,因为没有匹配项。
再举一个例子:
public class Exption<T extends Exception> {
public static void main(String[] args) {
try {
new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
}catch (final RuntimeException ex){
ex.printStackTrace();
System.out.println("done");
}
}
private void pleaseThrow(final Exception t) throws T{
throw (T)t;
}
}
这运行良好并且调用了catch块。 JVM 知道对象类型将是IllegalArgumentException 并且 RuntimeException 是超类,因此,由于超类能够引用子类对象,所以它匹配。
现在,让我们回到你的代码。
您只编写了一个由SqlException 组成的catch 块,这是一个checked exception,因此不能从pleaseThrow() 抛出,因为它根据编译器抛出RuntimeException。
因此,会生成此错误:
Error:(9, 10) java: exception java.sql.SQLException is never thrown in body of corresponding try statement
如您所知,在 Java 中使用 catch 块捕获从未抛出的已检查表达式是非法的。
现在,让我们来看看您的代码的第 2 版:
public class Exption<T extends Exception> {
public static void main(String[] args) {
try {
new Exption<RuntimeException>().pleaseThrow(new SQLException());
}catch(final RuntimeException e){
e.printStackTrace();
System.err.println("caught");
}
}
private void pleaseThrow(final Exception t) throws T{
throw (T)t;
}
}
现在,在你写的RuntimeException 的 catch 块中,事情变得有意义了。
编译器看到该方法抛出 RuntimeException 并且在 catch 块中我们也有运行时异常。
现在,让我们仔细看看。编译器很容易编译这段代码并将其发送到 JVM。但在此之前,它会进行类型擦除。
让我们看看类型擦除会对我们的代码造成什么影响:
[在实际提供给 JVM 的代码中,是字节级代码。下面的示例显示了擦除对代码的作用。请注意,如果您尝试编译,以下代码将无法在编译器中正常运行,因为此代码是在类型擦除之后并且几乎没有丢失编译器需要的细节。然而,JVM 可以运行与此等效的字节码。]
public class Exption {
public static void main(String[] args) {
try {
new Exption().pleaseThrow(new SQLException());
}catch(final RuntimeException e){
e.printStackTrace();
System.err.println("caught");
}
}
private void pleaseThrow(final Exception t) throws java.lang.Exception {
throw (java.lang.Exception) t;
}
}
要了解这段代码是如何简化为 的,您需要阅读有关类型擦除的内容。不过暂时相信我吧。
现在,这段代码是一段非常有趣的代码。
JVM 直接在这段代码上运行。如果你看到 JVM 知道它方法会抛出一个 SqlException 类型的 Object 类型转换为 Exception。它试图在 catch 块中找到匹配但没有匹配。 RunTimeException 不是SqlException 的超类。因此,没有调用 catch 块。
让我们修改代码以更了解它。
public class Exption<T extends Exception> {
public static void main(String[] args) {
try {
new Exption<RuntimeException>().pleaseThrow(new SQLException());
}catch(final RuntimeException e){
e.printStackTrace();
System.err.println("caught");
}catch(Exception r){
System.out.println("done");
}
}
private void pleaseThrow(final Exception t) throws T{
throw (T)t;
}
}
这将输出“完成”。