checkedexception 必须由编译器强制处理,runtimeexception 则由开发者主动预防而非捕获;前者如 ioexception 需 try-catch 或 throws,后者如 nullpointerexception 应通过空检查等防御性编程规避。

RuntimeException 不需要你立刻处理,CheckedException 必须马上回应——不是“该不该捕获”,而是“编译器逼不逼你写代码”。
编译器是否报错,是唯一硬指标
Java 编译器对 IOException 这类 CheckedException 会直接拒绝编译:没 try-catch,也没在方法签名加 throws IOException,就过不了。而 NullPointerException 或 ArrayIndexOutOfBoundsException 属于 RuntimeException,哪怕整段代码裸奔着抛,javac 也一声不吭。
这不是设计疏忽,是刻意为之:CheckedException 表示“这事外部可能出岔子(比如文件被删、网络断了),你得想好怎么兜底”;RuntimeException 则代表“你代码写错了(比如没判空就调 str.length()),别靠 catch 掩盖,赶紧修。”
- 常见 CheckedException:
IOException、SQLException、ClassNotFoundException - 常见 RuntimeException:
NullPointerException、IllegalArgumentException、ArithmeticException
throws 声明的语义完全不同
给方法加 throws IOException,是在告诉调用者:“我可能搞不定这个,你来决定是接住还是继续往上扔。”但加 throws RuntimeException 几乎没意义——调用方既不强制处理,也不因此获得额外信息。
更关键的是:throws 对 RuntimeException 是可选装饰,对 CheckedException 是强制契约。
- 如果你写
void readFile() throws IOException,调用方必须try-catch或继续throws - 如果你写
void divide(int a, int b) throws ArithmeticException,编译通过,但毫无约束力——调用方完全可以当它不存在
捕获它们的动机和后果截然不同
捕获 IOException 通常是为了重试、换路径、给用户友好提示;而捕获 NullPointerException 往往是危险信号——说明你本该在之前做空检查,却选择用异常流程掩盖逻辑漏洞。
防御性编程在这里起效远比 try-catch 稳定:
- 错误做法:
try { obj.doSomething(); } catch (NullPointerException e) { /* 忽略或打日志 */ } - 正确做法:
if (obj != null) { obj.doSomething(); }
Runtime Exception 的本质不是“可以忽略”,而是“应该前置拦截”。一旦它真抛出来,往往意味着某个 if 没写,某个参数校验漏了,或者 contract 被破坏了。
自定义异常时,继承谁决定了它的脾气
你写一个 InvalidConfigException,如果让它继承 Exception,那所有用到它的方法都得显式处理;如果继承 RuntimeException,它就变成“配置错得离谱,程序不该带病运行”的意思。
选哪条路,取决于你想传递什么信号:
- 继承
Exception→ “这问题可能发生,但系统应尝试恢复” - 继承
RuntimeException→ “这问题不该发生,发生了就说明调用方或当前逻辑有严重缺陷”
最常被忽略的一点:很多团队把本该是 RuntimeException 的校验异常(比如 UserIdMustBePositiveException)做成 Checked,结果满屏 try-catch 却不修复源头,反而让错误更难定位。










