throw:手动抛异常
就是你在代码里发现了不对劲的情况,主动”报警”
比喻:你是餐厅服务员,发现菜里有虫子,你主动喊”有问题!”——这就是throw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test public void testThrow() { System.out.println("=== throw 手动抛异常 ===");
try { setAge(-5); } catch (IllegalArgumentException e) { System.out.println("捕获到异常:" + e.getMessage()); } }
public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("年龄不能为负数,你输入的是:" + age); } System.out.println("年龄设置成功:" + age); }
|
throw的语法:throw new 异常类名("异常描述信息");
throw后面的代码不会执行,因为抛异常就相当于”这个方法到此为止了”
throws:声明异常
写在方法签名上,告诉调用者”我这个方法可能会出事,你自己小心”
比喻:快递包裹上写”易碎品”——不是说它一定会碎,而是提醒你小心处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void testThrows() { System.out.println("=== throws 声明异常 ===");
try { readFile("not_exist.txt"); } catch (IOException e) { System.out.println("调用者处理异常:" + e.getMessage()); } }
public void readFile(String path) throws IOException { FileInputStream fis = new FileInputStream(path); fis.read(); fis.close(); }
public void riskyMethod() throws IOException, SQLException {
}
|
关键点
throws是给受检异常(Checked Exception)用的
非受检异常(RuntimeException及其子类)不需要throws声明(但你写了也不报错)
throws只是”声明”,不是”处理”,真正的处理还得靠 try-catch-finally
throw vs throws 对比表
| 对比项 |
throw |
throws |
| 位置 |
方法体内 |
方法签名上 |
| 作用 |
手动抛出一个异常对象 |
声明方法可能抛出的异常 |
| 后面跟什么 |
异常对象(new XxxException(...)) |
异常类名(可以多个,逗号分隔) |
| 数量 |
一次只能throw一个 |
可以声明多个 |
| 谁来处理 |
当前方法的调用者 |
当前方法的调用者 |
| 语法示例 |
throw new IOException("出错了"); |
void read() throws IOException |
| 必须用吗 |
主动抛异常时用 |
受检异常不处理时必须声明 |
异常传播链
异常就像烫手山芋,一层一层往上抛,直到有人接住为止
1 2 3 4 5 6 7 8 9
| methodC() 抛出异常 ↓ methodC没有try-catch methodB() 接到异常,也没处理 ↓ methodB也没有try-catch methodA() 接到异常,也没处理 ↓ methodA也没有try-catch main() 接到异常,还是没处理 ↓ JVM收到异常 → 打印堆栈信息 → 程序崩溃
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Test public void testExceptionChain() { System.out.println("=== 异常传播链 ==="); try { methodA(); } catch (Exception e) { System.out.println("最终在这里被接住了"); e.printStackTrace(); } }
public void methodA() throws Exception { System.out.println("进入methodA"); methodB(); }
public void methodB() throws Exception { System.out.println("进入methodB"); methodC(); }
public void methodC() throws Exception { System.out.println("进入methodC"); throw new Exception("methodC出事了!"); }
|
堆栈信息(Stack Trace)
出异常后打印的那一坨信息,从下往上读就是异常传播的路径
最上面一行是异常发生的地方,往下是一层层的调用者
看到异常先看第一行,那就是出问题的代码位置
实际开发中的套路
套路1:底层throw,上层catch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public User findUser(Long id) { User user = userDao.findById(id); if (user == null) { throw new RuntimeException("用户不存在,ID:" + id); } return user; }
@GetMapping("/user/{id}") public Result getUser(@PathVariable Long id) { try { User user = userService.findUser(id); return Result.success(user); } catch (RuntimeException e) { return Result.fail(e.getMessage()); } }
|
套路2:异常转换(包装异常)
底层抛的异常太底层了(比如SQLException),上层不想知道这些细节
把底层异常包装成业务异常,再往上抛
1 2 3 4 5 6 7 8 9
| public void saveUser(User user) { try { userDao.insert(user); } catch (SQLException e) { throw new RuntimeException("保存用户失败", e); } }
|
更好的做法是用 自定义异常 代替 RuntimeException
常见坑
坑1:throws声明了异常但调用者不处理
如果是受检异常,编译器会报错,强制你处理
如果是非受检异常,编译器不管,但运行时会崩——所以别掉以轻心
坑2:throw后面还写代码
1 2
| throw new RuntimeException("出错了"); System.out.println("这行永远执行不到");
|
坑3:catch了异常又原样throw,但丢了原始信息
1 2 3 4 5 6 7 8 9
| catch (SQLException e) { throw new RuntimeException("操作失败"); }
catch (SQLException e) { throw new RuntimeException("操作失败", e); }
|
坑4:在循环里throw异常来控制流程
异常的创建和抛出开销很大,别用它代替break/continue
练习题
题1:throw和throws的区别是什么?各写一行示例
throw:方法体内,手动抛出异常对象。throw new IllegalArgumentException("参数错误");
throws:方法签名上,声明可能的异常。public void read() throws IOException {}
题2:以下代码有什么问题?
1 2 3
| public void divide(int a, int b) throws ArithmeticException { System.out.println(a / b); }
|
答案:ArithmeticException 是RuntimeException的子类(非受检异常),写throws没有实际意义,不会强制调用者处理。更好的做法是在方法内用if判断 b == 0 然后throw一个有意义的异常信息
题3:写一个方法,接收字符串参数,如果是null或空字符串就抛异常
1 2 3 4 5 6
| public void validateName(String name) { if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("名字不能为空"); } System.out.println("名字合法:" + name); }
|
相关笔记
异常体系 | try-catch-finally | 自定义异常