先看题目:
public class Mocker<T extends Exception> { private void pleaseThrow(final Exception t) throws T { throw (T) t; } public static void main(final String[] args) { //原题 try { new Mocker<RuntimeException>().pleaseThrow(new SQLException()); } catch (final SQLException ex) { ex.printStackTrace(); } } }
问,这段代码会出现什么错误?
a.编译错误,因为没有SQLException被抛出 b.抛出ClassCastException,因为SQLException并不是RuntimeException的一个实例 c.没有错误,程序打印出抛出的SQLException堆栈跟踪信息 d.编译错误,因为我们不能将SQLException类型转换成RuntimeException
首先,我们知道,RuntimeException和SQLException都继承自Exception,但是在这个代码中RuntimeException是未检查的异常, 而SQLException是受检异常。
Java的泛型并不是具体化的。这意味着在编译时,泛型的类型信息会“丢失”,并且泛型参数像是被它的限定类型替换了一样, 或者当限定类型不存在时,泛型参数被替换成了Object。这就是大家所说的类型“擦除”。
通常我们会想,第七行会产生一个编译错误,因为我们不能将SQLException转换成RuntimeException, 但是这并不会发生。发生的是将T替换成了Exception,因为我们有:
throw (Exception) t;
pleaseThrow方法期望一个Exception,T被替换成了Exception,因此类型转换被擦除了,就像没写这个代码一样。
因此不会有类型转换异常,排除b和d选项。
那么,如果代码编译通过了,应该变成这样
private void pleaseThrow(Exception t) throws T { throw t; }
对于不知情的旁观者来说,代码中并没有SQLException,而编译器也是这么认为的。
所以最终答案是a.编译错误,因为没有SQLException被抛出。
然而,SQLException确实是被抛出了,我们需要向编译器证明这一点,我们将异常捕获改成RuntimeException
//改成RuntimeException后 try { new Mocker<RuntimeException>().pleaseThrow(new SQLException()); } catch (final RuntimeException ex) { ex.printStackTrace(); }
编译没有问题,运行
Exception in thread "main" java.sql.SQLException at com.happy.exam.Mocker.main(Mocker.java:97)
最终,虚拟机捕获了该异常,为我们提供了非常有力的证据。