catch块必须按子类异常到父类异常的顺序排列,否则编译失败;同级异常顺序任意;multi-catch要求异常互不继承;finally或try-with-resources不改变该规则。

catch块顺序必须从子类异常到父类异常
Java编译器会严格检查catch块的声明顺序:如果父类异常(比如Exception)写在子类异常(比如NullPointerException)前面,编译直接失败,报错error: exception NullPointerException has already been caught。这不是运行时行为,是编译期强制约束。
原因很简单:异常匹配是按代码顺序逐个比对的,一旦某个catch能接住,后续就跳过了。子类异常对象天然属于父类类型,所以父类catch放在前面,子类根本没机会执行。
- 正确顺序:
catch (IOException e)→catch (Exception e) - 错误顺序:
catch (Exception e)→catch (IOException e)(编译不过) - 同级异常(无继承关系)顺序无所谓,比如
IOException和SQLException
同一个try里不能重复捕获相同类型或其子类型
即使两个catch块参数名不同、处理逻辑不同,只要类型相同或存在继承关系(比如RuntimeException和IllegalArgumentException),编译器就会拒绝。
常见踩坑场景:重构时复制粘贴了旧catch块,或误以为“多捕获一次更保险”。其实这既不合法,也不必要——异常只会被第一个匹配的catch处理。
立即学习“Java免费学习笔记(深入)”;
- 不允许:
catch (RuntimeException e)后面再跟catch (IllegalArgumentException e) - 允许:
catch (IOException e)和catch (SQLException e)(二者无继承) - 注意:自定义异常若继承了已存在的
catch类型,也受此规则限制
使用multi-catch(Java 7+)可简化并列异常处理
当多个异常需要执行完全相同的处理逻辑(比如统一记录日志后重新抛出),用|分隔的multi-catch比写多个结构雷同的catch更安全、更简洁,还能避免手误导致顺序错误。
但要注意:multi-catch中列出的所有异常必须互不兼容(即不能有继承关系),否则编译报错error: Alternatives in a multi-catch statement cannot be related by subclassing。
- 合法:
catch (IOException | SQLException e) - 非法:
catch (Exception | RuntimeException e)(后者是前者子类) - 异常变量
e在multi-catch中是final,不可重新赋值 - 类型推导结果是这些异常的最近公共父类(如
IOException | SQLException→Exception)
finally或try-with-resources不会改变catch匹配逻辑
有人误以为加了finally或用了try-with-resources,就能“绕过”catch顺序限制,其实完全不影响。异常匹配只取决于try块抛出的异常类型和catch块的声明顺序。
真正容易被忽略的是:如果finally块里也抛异常,它会覆盖try中已捕获但未处理完的异常(比如在catch里没显式throw,而finally抛了新异常),这时原异常就丢失了。
- 想保留原始异常?在
finally里别随便throw,或用addSuppressed()补救 - try-with-resources的
close()异常默认会被抑制,但前提是主异常已存在且未被吞掉 - catch顺序规则在任何try结构下都生效,和资源管理无关










