throw只能出现在方法体内的可执行路径上,如参数校验、异常状态码处理、非法状态转移时;抛出后若未被捕获则方法立即退出,后续语句不可达。

throw 用在哪儿?不是所有地方都能随便 throw
Java 里 throw 是语句,不是表达式,只能出现在方法体内部、且必须在可执行路径上。常见误用是写在方法签名后、类字段声明里,或者 if 条件不成立的分支末尾却忘了加 return,导致编译报错 “unreachable statement”。
典型可用位置:
- 参数校验失败时,比如
if (id == null) throw new IllegalArgumentException("id cannot be null"); - 调用下游接口返回异常状态码后,主动转为 Java 异常
- 自定义状态机中,遇到非法状态转移时中断流程
注意:throw 不会自动结束方法——它抛出异常后控制权交由调用栈,但当前方法若没被 try-catch 捕获,就会立即退出;如果写了 throw 后还跟了普通语句(没加 return 或没在 catch/finally 中处理),编译器直接拒绝。
抛什么异常?RuntimeException 和 Exception 的选择逻辑
选哪类异常,本质是在回答:“这个错误该由谁来处理?”
RuntimeException 及其子类(如 NullPointerException、IllegalArgumentException)是 unchecked 异常,调用方无需强制捕获或声明。适合表示编程错误或不可恢复的逻辑问题,比如传入负数给只接受正数的函数。
Exception 及其非 RuntimeException 子类(如 IOException、SQLException)是 checked 异常,方法签名必须用 throws 声明,调用方必须处理。适合表示外部依赖失败等可预期但需应对的情况。
实操建议:
- 自己写的业务校验,优先用
IllegalArgumentException、IllegalStateException,别动不动 newException() - 封装工具类时,如果底层调用了
Files.readAllBytes()这种抛IOException的 API,你又不想暴露 IO 细节,就 catch 后 re-throw 为RuntimeException子类(比如自定义ResourceLoadException) - 不要为了“绕过编译检查”把 checked 异常包装成
RuntimeException后吞掉——这会让调用方彻底失去处理机会
自定义异常怎么写才不算白写?构造函数和 message 的取舍
很多自定义异常类只写了空构造和一个 String 构造,结果日志里只有 “UserNotFoundException”,看不出是哪个 userId 没找到,排查时还得翻代码打点。
真正有用的自定义异常至少要支持:
- 带业务字段的构造函数,比如
UserNotFoundException(long userId),内部存下userId并重写getMessage()返回含 ID 的描述 - 保留原始异常的 cause(通过
super(message, cause)),尤其在包装第三方异常时,否则堆栈断层 - 避免在
getMessage()里拼接敏感信息(如密码、token),防止日志泄露
示例片段:
public class UserNotFoundException extends RuntimeException {
private final long userId;
public UserNotFoundException(long userId) {
super("User not found: " + userId);
this.userId = userId;
}
// getter 可选,方便上层做条件判断
public long getUserId() { return userId; }
}
throw new XXXException() 之后,为什么 try-catch 还没生效?
最常见原因是异常类型不匹配:你 throw 的是 CustomException,但 catch 写的是 Exception —— 看似能捕获,其实没问题;但如果 catch 写成了 RuntimeException,而你抛的是 Exception 子类,那就根本进不去。
另一个隐蔽坑是异常被更外层的通用 handler 吞掉了。比如 Spring MVC 里加了 @ControllerAdvice 全局捕获 Exception,但你在 service 层 throw 了一个自定义 BusinessException,结果日志只看到 “handled by GlobalExceptionHandler”,却没在 controller 里看到预期的 catch 分支执行。
调试建议:
- 打断点确认
throw执行到了,再看调用栈是否真走到你期望的catch块 - 检查
catch的异常类型是否是throw类型的父类,且没被中间某层提前捕获 - 如果用了 AOP 或框架拦截(如 Feign、Dubbo),异常可能在序列化/反序列化阶段被转换或屏蔽
复杂点在于:异常传播路径受 JVM 栈帧、代理机制、异步线程上下文共同影响,单靠看 throw 那一行代码,没法确定它最终在哪被捕获或终结。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











