Java 的异常是 class,它的继承关系如下:

  1. ┌───────────┐
  2. │ Object │
  3. └───────────┘
  4. ▲
  5. │
  6. ┌───────────┐
  7. │ Throwable │
  8. └───────────┘
  9. ▲
  10. ┌─────────┴─────────┐
  11. │ │
  12. ┌───────────┐ ┌───────────┐
  13. │ Error │ │ Exception │
  14. └───────────┘ └───────────┘
  15. ▲ ▲
  16. ┌───────┘ ┌────┴──────────┐
  17. │ │ │
  18. ┌─────────────────┐ ┌─────────────────┐┌───────────┐
  19. │OutOfMemoryError │... │RuntimeException ││IOException│...
  20. └─────────────────┘ └─────────────────┘└───────────┘
  21. ▲
  22. ┌───────────┴─────────────┐
  23. │ │
  24. ┌─────────────────────┐ ┌─────────────────────────┐
  25. │NullPointerException │ │IllegalArgumentException │...
  26. └─────────────────────┘ └─────────────────────────┘

Throwable 是异常体系的根,它继承自 Object。Throwable 分为 Error 和 Exception 两类,Error 表示严重的错误,而 Exception 则是运行时的错误,通常都会捕获并处理。Exception 又细分为 RuntimeException 和 非 RuntimeException(IOException等)。

通常情况下,Error 是无需捕获的严重错误,Exception 是应该捕获的可处理的错误。RuntimeException 及其子类无需强制捕获,非 RuntimeException(Checked Exception)需强制捕获,或者用 throws 声明;

捕获异常

多 catch 语句

可以使用多个 catch 语句,每个 catch 分别捕获对应的 Exception 及其子类。JVM 在捕获到异常后,会从上到下匹配 catch 语句,匹配到某个 catch 后,执行 catch 代码块,然后不再继承匹配。
使用多 catch 语句时,子类 Exception catch语句必须写在前面,否则异常会被父类捕获处理

  1. public static void main(String[] args) {
  2. try {
  3. process1();
  4. process2();
  5. process3();
  6. } catch (UnsupportedEncodingException e) {
  7. // UnsupportedEncodingException 是 IOException 子类
  8. System.out.println("Bad encoding");
  9. } catch (IOException e) {
  10. System.out.println("IO error");
  11. }
  12. }

finally 语句

finally 语句中声明了必须执行的代码。如果没有发生异常,就正常执行 try {...} 语句块,然后执行 finally 语句。如果发生了异常,就中断执行 try {...} 语句块,然后跳转执行匹配的 catch 语句块,最后执行 finally 语句块。在 catch 语句块中使用 return 语句不会影响 finally 语句块的执行。

  1. var str = "a";
  2. try {
  3. int ii = Integer.parseInt(str);
  4. } catch (NumberFormatException e) {
  5. e.printStackTrace();
  6. System.out.println("catch");
  7. return;
  8. } finally {
  9. System.out.println("finally");
  10. }

常用异常

Java 标准库定义的常用异常如下:

  1. Exception
  2. │
  3. ├─ RuntimeException
  4. │ │
  5. │ ├─ NullPointerException
  6. │ │
  7. │ ├─ IndexOutOfBoundsException
  8. │ │
  9. │ ├─ SecurityException
  10. │ │
  11. │ └─ IllegalArgumentException // 参数检查不合法
  12. │ │
  13. │ └─ NumberFormatException // 数值类型格式化错误
  14. │
  15. ├─ IOException
  16. │ │
  17. │ ├─ UnsupportedCharsetException
  18. │ │
  19. │ ├─ FileNotFoundException
  20. │ │
  21. │ └─ SocketException
  22. │
  23. ├─ ParseException
  24. │
  25. ├─ GeneralSecurityException
  26. │
  27. ├─ SQLException
  28. │
  29. └─ TimeoutException

自定义异常

项目开发中,通常会封装包含业务语义的自定义的异常。常见做法是自定义一个 BaseException 作为“根异常”,然后派生出各种对应业务语义类型的异常。自定义异常通常还和错误码设计搭配组合使用

  1. // 根异常
  2. public class BaseException extends RuntimeException {
  3. public BaseException(String message, Throwable cause) {
  4. super(message, cause);
  5. }
  6. public BaseException(String message) {
  7. super(message);
  8. }
  9. public BaseException(Throwable cause) {
  10. super(cause);
  11. }
  12. }
  13. // 不包含该用户、用户未找到
  14. public class UserNotFoundException extends BaseException {
  15. }
  16. // 未登录、登录失败
  17. public class LoginFailedException extends BaseException {
  18. }

NullPointerException

NullPointerException 空指针异常,简称 NPE。如果一个对象为 null ,调用其方法或访问其字段就会产生 NullPointerException。 实际上,NullPointerException 是一种代码逻辑错误,一个好的编码习惯可以极大的降低这种异常的产生。

  • 成员变量在定义时给定初始值
  • String 类型默认值使用 "" 空字符串而不是默认的 null
  • 方法返回时尽量避免返回 null,而是返回 "" 空字符、空对象、空集合。如果调用方需要用到 null,推荐返回 Optional<T> 对象,调用方可以通过 Optional.isPresent() 方法判断是否有结果
    1. public Optional<String> readFromFile(String file) {
    2. if (!fileExist(file)) {
    3. return Optional.empty();
    4. }
    5. ...
    6. }