Java 异常处理:如何写出“正确”但被编译器认为有语法错误的程序
摘要:文章的标题看似自相矛盾。
本文分享自华为云社区《Java异常处理:如何写出“正确”但被编译器认为有语法错误的程序》,作者: Jerry Wang 。
文章的标题看似自相矛盾,然而我在“正确”二字上打了引号。我们来看一个例子,关于 Java 异常处理(Exception Handling)的一些知识点。
看下面这段程序。方法 pleaseThrow 接受一个 Exception 的实例,然后简单地将该实例抛出。然后调用这个方法时,我传入了一个 SQLException 的实例。因为 pleaseThrow 的调用包裹在一个 try catch 块里。
问题:plesseThrow 方法抛出的 SQLException 可以成功被 catch 住么?
答案:上面这段代码有语法错误,不能通过编译!
我们来一步步分析。
Java 类 ExceptionForQuiz<T extends Exception>使用了一个泛型语法,Textends Exception 意思是这个泛型类实例化的时候,传入的类型参数 T 必须是 Exception 以及它的子类。
我在实例化类 ExceptionForQuiz 时,传入的类型参数是 RuntimeException。
RuntimeException 在 Java 里是一种 Unchecked 异常,即使一个方法运行时可能会抛出 RuntimeException,也不需要开发人员在方法前用代码显式声明。
看 JDK RuntimeException 的注释说的很清楚:Unchecked exceptions do NOT need to be declared in a method or constructor’s clause if they can be thrown by the execution of the method or constructor.
这个作者 Frank Yellin 一定是个大牛。
因为泛型是 Java 1.5 版本才引进的概念,关于泛型有一个类型擦除的概念,即**泛型信息只存在于代码编译阶段,编译之后的代码里,与泛型相关的信息会被擦除掉。**比如之前泛型类中的类型参数部分如果没有指定上限,像这种写法<T>, 则会被转译成普通的 Object 类型。如果指定了上限如<T extends String>则类型参数就被替换成类型上限。
为了简化起见,我们先把代码里的 try catch 块去掉。
下面是从 ExceptionForQuiz.class 反编译之后的代码:
我们从上图能观察到,方法 pleaseThrow 和雷 ExceptionForQuiz 的泛型参数 RuntimeException 已经被擦除掉了。pleaseThrow 这个方法能抛出的异常类型已经被擦除成为 Exception 了。
使用 javap 观察编译生成的字节码,同样能发现类型参数 RuntimeException 被擦除的事实:
看第二个红色高亮区域:Exceptions: throw java.lang.Exception
现在我们来看编译器会报什么错误消息:Unreachable catch block for SQLException. This exception is never thrown from the try statement body.
根据异常类型擦除的事实,这个错误消息是合理的,因为 pleaseThrow 方法的声明现在只能抛出类型为 Exception 的异常,所以第 14 行的 catch 永远也没有办法接收到类型为 SQLException 的异常,所以编译器抛出错误。
如何消除掉这个编译器错误呢?把第 14 行的 SQLException 改成 RuntimeException 即可。
但是这样的话,虽然消除了语法错误,但是方法 pleaseThrow 抛出的 SQLException 没有办法被 catch 住,会报运行时错误:
如何把 pleaseThrow 抛出的 SQLException 也用 catch 语句接住呢?将第 14 行的 RuntimeException 改成所有异常的超类:Exception。
再次执行,这次既没有语法错误,也没有运行时错误了:SQLException 已经成功地被第 14 行的 catch 语句捕捉住了。
点击关注,第一时间了解华为云新鲜技术~
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/c2c4775ab1a265cfe33629dcb】。文章转载请联系作者。
评论