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

文章的标题看似自相矛盾,然而我在“正确”二字上打了引号。我们来看一个例子,关于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>使用了一个泛型语法,T extends 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语句捕捉住了。

要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码:

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android干货

Python常用模块:datetime

注:Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。

1032
来自专栏py+selenium

[笨方法学python]习题51自动化测试笔记

本节自动化测试部分看不大懂,自己每步都打印出来,帮助理解。(代码标红部分为自己加入调试为打印变量值所用)

2092
来自专栏JackeyGao的博客

Python 可变参数的坑

Python 的可变参数有*args的位置可变参数和**kwargs参数可变两种. 今天在DEBUG的时候发现了一个非常棘手的**kwargs的坑.

992
来自专栏爱撒谎的男孩

Spring表达式和自动装配

4298
来自专栏Java学习之路

02 Spring框架 简单配置和三种bean的创建方式

整理了一下之前学习Spring框架时候的一点笔记。如有错误欢迎指正,不喜勿喷。 上一节学习了如何搭建SpringIOC的环境,下一步我们就来讨论一下如何利...

3215
来自专栏Golang语言社区

Go语言锁的解读

var l sync.Mutexvar a stringfunc f() { a = "hello, world" l.Unlock() }func mai...

3215
来自专栏大内老A

yield在WCF中的错误使用——99%的开发人员都有可能犯的错误[上篇]

在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题。这里要说的是另一个问题:对于返回类型为...

1818
来自专栏Java学习之路

05 Spring框架 依赖注入(二)

上一节我们讲了三种信息的注入,满足一个类的属性信息的注入,但是如果我们需要向一个实例中注入另一个实例呢?就像我们创建一个学生类,里边有:姓名,性别,年龄,成绩等...

2845
来自专栏互联网杂技

前端异步代码解决方案实践(二)

早前有针对 Promise 的语法写过博文,不过仅限入门级别,浅尝辄止食而无味。后面一直想写 Promise 实现,碍于理解程度有限,多次下笔未能满意。一拖再拖...

2266
来自专栏程序员互动联盟

【编程基础】C语言常见宏定义

我们在使用C语言编写程序的时候,常常会使用到宏定义以及宏编译指令,有的可能比较常用,有的可能并不是很常用,是不是所有的C语言宏定义以及宏指令你都清楚呢? 指令 ...

3748

扫码关注云+社区

领取腾讯云代金券