异常层次结构
Java 中所有异常都继承自 Throwable 类,分为两大分支:Error 和 Exception。
Throwable ├── Error (系统级错误,不可处理) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── NoClassDefFoundError └── Exception (程序级异常,可处理) ├── IOException (受检异常) ├── SQLException (受检异常) └── RuntimeException (非受检异常) ├── NullPointerException ├── ArrayIndexOutOfBoundsException ├── ClassCastException └── IllegalArgumentException
|
- Error:JVM 层面的错误,程序无法处理,也不应尝试捕获
- Exception:程序可以捕获并处理的异常
受检异常 vs 非受检异常
| 类型 |
父类 |
编译器检查 |
典型场景 |
| 受检异常 (Checked) |
Exception (非 RuntimeException) |
必须显式处理 |
IO操作、数据库操作 |
| 非受检异常 (Unchecked) |
RuntimeException |
不强制处理 |
空指针、数组越界 |
public void readFile(String path) throws IOException { FileReader reader = new FileReader(path); }
public int divide(int a, int b) { return a / b; }
|
try-catch-finally
最基础的异常处理结构:
public void demo() { try { int[] arr = new int[5]; arr[10] = 1; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组越界:" + e.getMessage()); } finally { System.out.println("无论如何都会执行"); } }
|
多个 catch 块的匹配规则:从上到下匹配,子类异常必须写在父类异常前面:
try { } catch (FileNotFoundException e) { } catch (IOException e) { } catch (Exception e) { }
|
finally 的执行时机——即使在 try 或 catch 中执行了 return,finally 仍会执行:
public static int test() { try { return 1; } finally { System.out.println("finally 执行了"); } }
|
唯一不执行 finally 的情况:调用了 System.exit(0) 或 JVM 崩溃。
try-with-resources (JDK 7+)
自动关闭实现了 AutoCloseable 接口的资源,无需手动写 finally:
BufferedReader br = null; try { br = new BufferedReader(new FileReader("test.txt")); System.out.println(br.readLine()); } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } }
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { System.out.println(br.readLine()); } catch (IOException e) { e.printStackTrace(); }
|
多个资源可以同时声明,用分号分隔:
try (FileInputStream fis = new FileInputStream("in.txt"); FileOutputStream fos = new FileOutputStream("out.txt")) { }
|
throw vs throws
| 关键字 |
位置 |
作用 |
throw |
方法体内部 |
抛出一个异常对象 |
throws |
方法签名上 |
声明方法可能抛出的异常类型 |
public void checkAge(int age) throws IllegalArgumentException { if (age < 0) { throw new IllegalArgumentException("年龄不能为负数:" + age); } }
|
自定义异常
public class InsufficientBalanceException extends Exception { public InsufficientBalanceException(String message) { super(message); } }
public class BusinessException extends RuntimeException { private String errorCode;
public BusinessException(String errorCode, String message) { super(message); this.errorCode = errorCode; }
public String getErrorCode() { return errorCode; } }
|
异常链
将一个异常作为另一个异常的原因,保留完整的调用栈:
try { } catch (SQLException e) { throw new BusinessException("DB001", "数据查询失败", e); }
|
最佳实践
- 不要吞掉异常——永远不要写空的 catch 块:
try { doSomething(); } catch (Exception e) { }
try { doSomething(); } catch (Exception e) { log.error("处理失败", e); throw new ServiceException("操作失败", e); }
|
优先使用标准异常——能用 JDK 自带的就别自定义(如 IllegalArgumentException、IllegalStateException)
异常信息要明确——包含关键参数和上下文:
throw new IllegalArgumentException("用户ID无效: " + userId);
|
尽早抛出,延迟捕获——底层发现问题立即抛出,在能处理的地方才捕获
不要在循环中使用 try-catch——把 try-catch 放在循环外面,避免性能损耗