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

  1. ┌───────────┐
  2. Object
  3. └───────────┘
  4. ┌───────────┐
  5. Throwable
  6. └───────────┘
  7. ┌─────────┴─────────┐
  8. ┌───────────┐ ┌───────────┐
  9. Error Exception
  10. └───────────┘ └───────────┘
  11. ┌───────┘ ┌────┴──────────┐
  12. ┌─────────────────┐ ┌─────────────────┐┌───────────┐
  13. OutOfMemoryError │... RuntimeException ││IOException│...
  14. └─────────────────┘ └─────────────────┘└───────────┘
  15. ┌───────────┴─────────────┐
  16. ┌─────────────────────┐ ┌─────────────────────────┐
  17. NullPointerException IllegalArgumentException │...
  18. └─────────────────────┘ └─────────────────────────┘

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

通常情况下,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. ├─ RuntimeException
  3. ├─ NullPointerException
  4. ├─ IndexOutOfBoundsException
  5. ├─ SecurityException
  6. └─ IllegalArgumentException // 参数检查不合法
  7. └─ NumberFormatException // 数值类型格式化错误
  8. ├─ IOException
  9. ├─ UnsupportedCharsetException
  10. ├─ FileNotFoundException
  11. └─ SocketException
  12. ├─ ParseException
  13. ├─ GeneralSecurityException
  14. ├─ SQLException
  15. └─ 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. }