Method.getExceptionTypes()仅返回显式声明的受检异常(继承Exception但非RuntimeException子类),不包含RuntimeException及其子类、Error,也无法捕获@SneakyThrows等绕过throws声明的异常。

Java 反射中获取方法声明的异常类型,是理解其契约行为的关键一步。通过 Method.getExceptionTypes(),你能准确知道该方法在编译期承诺抛出哪些受检异常——而运行时异常(RuntimeException 及其子类)和 Error 不会出现在这个列表里,哪怕方法体内实际抛出了它们。
getExceptionTypes() 只返回受检异常
这个方法返回的是 Class<? extends Throwable>[],但内容仅限于明确在 throws 子句中声明的、继承自 Exception 但不继承自 RuntimeException 的类型。例如:
-
void readFile() throws IOException, SQLException→ 返回{IOException.class, SQLException.class} -
void riskyOp() throws RuntimeException→ 不会 出现在结果中(因为RuntimeException是非受检的) -
void unsafeCast() throws ClassCastException→ 同样不会出现(它是RuntimeException的子类)
如何区分受检与非受检异常类型
拿到异常类数组后,可用以下逻辑判断每个类是否属于受检异常:
- 若
clazz == RuntimeException.class || clazz == Error.class→ 非受检 - 若
RuntimeException.class.isAssignableFrom(clazz)→ 非受检(含所有运行时异常子类) - 若
Error.class.isAssignableFrom(clazz)→ 非受检 - 其余继承自
Exception的类 → 属于受检异常
反射无法捕获“偷偷抛出”的受检异常
像 @SneakyThrows 这类编译期字节码操作,会让受检异常绕过 throws 声明直接抛出。此时 getExceptionTypes() 返回空数组,但运行时仍可能抛出 IOException 等原始类型。这意味着:
- 反射结果反映的是源码层面的显式契约,不是运行时真实行为
- 调用方若依赖反射做异常检查(如框架自动包装),需额外考虑 Lombok 或手动
throw (RuntimeException) new XxxException()场景 - IDE 和 javac 不会报错,但静态分析工具或强契约校验逻辑可能失效
实用建议:安全调用反射获取的方法
当你通过反射调用一个未知方法,并希望统一处理异常时,推荐按如下顺序捕获:
- 先
catch (InvocationTargetException e),再用e.getTargetException()拿到原始异常 - 对原始异常做类型判断:是
RuntimeException或Error就重新抛出;是其他Exception就按业务逻辑处理或包装 - 不要只依赖
getExceptionTypes()来决定是否 try-catch —— 它漏掉了所有非受检路径

















