写点什么

Java 异常处理:如何写出“正确”但被编译器认为有语法错误的程序

  • 2022 年 2 月 10 日
  • 本文字数:1555 字

    阅读完需:约 5 分钟

摘要:文章的标题看似自相矛盾。


本文分享自华为云社区《Java异常处理:如何写出“正确”但被编译器认为有语法错误的程序》,作者: Jerry Wang 。


文章的标题看似自相矛盾,然而我在“正确”二字上打了引号。我们来看一个例子,关于 Java 异常处理(Exception Handling)的一些知识点。


看下面这段程序。方法 pleaseThrow 接受一个 Exception 的实例,然后简单地将该实例抛出。然后调用这个方法时,我传入了一个 SQLException 的实例。因为 pleaseThrow 的调用包裹在一个 try catch 块里。


问题:plesseThrow 方法抛出的 SQLException 可以成功被 catch 住么?

public class ExceptionForQuiz<T extends Exception> {
private void pleaseThrow(final Exception t) throws T {
throw (T) t;
}
public static void main(final String[] args) {
try {
new ExceptionForQuiz<RuntimeException>().pleaseThrow(new SQLException());
}
catch( final SQLException ex){
System.out.println("Jerry print");
ex.printStackTrace();
}
}
}
复制代码



答案:上面这段代码有语法错误,不能通过编译!


我们来一步步分析。


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 语句捕捉住了。


​​​​​​点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
Java异常处理:如何写出“正确”但被编译器认为有语法错误的程序