异常处理

Throwable — 所有非正常情况 父类

  • Java中所有 非正常情况分成两种:异常(Exception)和错误(Error),它们都继承 Throwable 父类
  • 实例方法
    • String getMessage():返回该异常的描述信息(提示给用户)
    • String toString():返回该异常的类型和描述信息(不用)
    • void printStackTrace():打印异常的跟踪栈信息到控制台,包括异常的类型、异常的原因、异常出现的位置(开发和调试)

异常分类

Pasted image 20240305234652

  • 编译时异常(Checked异常):除了 RuntimeException 及其子类以外,其它的 Exception 及其子类

    • 编译器要求必须处理的异常,因此程序中一旦出现这类异常,必须显式处理(捕获或抛出),否则编译无法通过
    • 常见的编译时异常:ParseException、InterruptedException、IOException(子类:FileNotFoundException)、ClassNotFoundException、SQLException
  • 运行时异常(Runtime 异常 / UnChecked 异常):RuntimeException 及其子类

    • 编译器不要求强制处理的异常,程序中可能出现这类异常时,可以不处理,但是一般要求需要 提前规避这类异常,这是可以提前避免的
    • Java 类库中定义的运行时异常类应由程序员预检查来规避,而不是捕获

try … catch 捕获异常

1
2
3
4
5
6
7
8
9
try {
	// 可能会出现异常的代码
} catch (要捕获的异常类型A e) {
	// 处理异常的代码:记录日志/打印异常信息/继续抛出异常等
} catch (要捕获的异常类型B e) {
	// 处理异常的代码:记录日志/打印异常信息/继续抛出异常等
} finally {
	// 关闭资源对象、流对象等
}

抛出/捕获异常:

  • 抛出异常:当程序运行出现异常,系统会自动生成一个异常对象,该异常对象被提交给 Java 运行时环境

  • 捕获(catch)异常:如果执行 try 块里的代码时出现异常,Java 运行时环境收到异常对象后,会寻找能处理该异常对象的 catch 块,如果找到合适的 catch 块,则把该异常对象交给该 catch 块处理。例如:上边有不同的catch块分支,需要找到能处理对应异常类型的catch块

  • Java 运行时环境找不到捕获该异常的 catch 块,则使用全局默认的DefaultUncaughtExceptionHandler(处理方式是 e.printStackTrace(System.err)),然后运行时环境终止,该线程也将退出

  • 处理多种异常类型时,必须先捕获子类类型异常后捕获父类类型异常,否则编译报错(最后捕获 Exception 类型异常,确保异常对象能被捕获到)

语法约定:

  • finally块:不管 try 块中的代码是否出现异常,也不管哪一个 catch 块被执行,甚至在 try 块或 catch 块中执行了 return 语句,finally 块总会被执行(除非在 try 块或会执行的 catch 块中调用退出 JVM 的相关方法)
    • 当程序执行 try 块、catch 块时遇到 return 或 throw 语句时,系统不会立即结束该方法,而是去寻找该异常处理流程中是否包含 finally 块,如果有 finally 块,系统立即开始执行 finally 块——只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、catch 块里的 return 或 throw 语句;如果 finally 块里也使用了 return 或 throw 等导致方法终止的语句,finally 块已经终止了方法,系统将不会跳回去执行 try 块、catch 块里的任何代码
  • 语法层面:try 块必须和 catch 块或和 finally 块同在,不能单独存在,catch 块或和 finally 块二者必须出现一个,finally 块必须位于所有的 catch 块之后

使用 throws 声明抛出异常

1
2
[修饰符] 返回值类型 方法名(参数列表) throws 异常类A, 异常类B, ...{
}
  • 在 可能出现异常的 方法上声明可能抛出的异常类型,用于表示当前方法不处理异常,提醒 该方法调用者 来处理异常。
  • 该异常将交给上一级调用者处理,调用者要么 try … catch,要么也 throws
  • main 方法也使用 throws 声明抛出异常,该异常将交给 JVM 处理,JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行

使用 throw 自行抛出异常

1
throw new 异常类("异常信息"); // 终止方法
  • 语法层面:throw 语句可以单独使用,throw 后面只能跟一个异常对象
  • 使用技巧:有返回值的方法中,可以使用 throw 来避免返回一个空值
  • return 或 throw 语句到所在结束的花括号之间不能有其它的语句,否则编译报错。这个IDE会自动检查
  • throw抛出不同异常,处理情况不同:
    • Checked 异常:该throw 语句必须处于 try 块里,或处于带 throws 声明的方法中
    • Runtime 异常:该语句无须放在 try 块里,也无须放在带 throws 声明抛出的方法中
  • 特殊情况:在 catch 块中使用 throw 语句,方法既可以捕获异常,还可以抛出异常给方法的调用者

自定义异常类型

  • 自定义 Checked 异常,应继承 Exception
  • 自定义 Runtime 异常,应继承 RuntimeException(推荐)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 自定义业务逻辑异常
public class LogicException extends RuntimeException {
	// 无参数的构造器
	public LogicException() {
		super();
	}
	// 带一个字符串参数的构造器
	public LogicException(String message) {
		super(message);
	}
	// message 当前异常的原因/信息;cause 当前异常的根本原因
	public LogicException(String message, Throwable cause) {
		super(message, cause);
	}
}

上边自定义 异常类,构造器调用的是 Throwable()中提供的几个构造方法,如下:

  • Throwable()
  • Throwable(String message)
  • Throwable(Throwable cause)
  • Throwable(String message, Throwable cause):构造一个带指定详细消息(以后通过 getMessage() 方法获取)和原因(以后通过 getCause() 方法获取)的新 throwable

异常转译 和 异常链

  • 异常转译:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息
  • 异常链:捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来

异常处理规则

  • 不要过度使用异常:
    • 对于完全已知的错误,应该编写处理这种错误的代码,增加程序的健壮性
    • 外部的、不能确定和预知的运行时错误才使用异常
  • 不要使用过于庞大的 try 块
  • 避免使用 Catch All 语句
  • 不要忽略捕获到的异常

附录

参考文献

版权信息

本文原载于kitebin.top,遵循CC BY-NC-SA 4.0协议,复制请保留原文出处。

Built with Hugo
主题 StackJimmy 设计