java异常
java异常是什么
Java 中的异常(Exception)是程序运行时偏离正常执行逻辑的非正常条件(区别于编译期语法错误),会中断代码的线性执行流程;所有异常均以对象形式存在,且最终继承自 java.lang.Throwable 类;Java 提供了标准化的异常处理机制,既允许程序对非致命异常进行捕获、处理或传递,避免程序非预期崩溃,又能通过异常对象保留完整的上下文信息,为问题定位和排查提供依据。
java异常体系
Java 异常体系是基于树形继承结构构建的标准化异常分类体系,核心根类是 java.lang.Throwable,所有异常 / 错误最终都继承自该类;其设计目的是区分 "程序可处理的非正常情况" 和 "JVM 级别的致命错误",并为不同类型的异常提供统一的处理范式。
java 异常体系如下图所示:

java.lang.Throwable 是 Java 中所有异常(Exception)和错误(Error)的超类,定义了 Java 异常处理机制的核心基础。
Exception 代表程序运行时发生的、可被程序捕获并合理处理的非正常条件,是开发者编码中唯一需要主动关注、捕获或传递的异常类型。
Error 是 JVM 或系统级别的致命错误,由 JVM 自动抛出,程序无法捕获、无法恢复,一旦发生会直接终止 JVM 进程。例如 OutOfMemoryError、StackOverflowError、NoClassDefFoundError、ThreadDeath 等。
Exception 的核心分类维度是编译期是否强制要求显式处理(Java 官方语言规范 JLS 的标准分类),分为受检异常(Checked Exception) 和非受检异常(Unchecked Exception) 两类;
日常开发中俗称的运行时异常与非运行时异常是该分类的通俗表述,二者的等价关系为:
非受检异常 ≡ 运行时异常(RuntimeException 及其子类);
受检异常 ≡ 非运行时异常(Exception 子类且非 RuntimeException 子类)。
受检异常(Checked Exception)是 Java 官方定义的异常类型,特指排除 RuntimeException 及其子类的所有 java.lang.Exception 子类;其核心特征是编译期由 Java 编译器强制校验:若方法的执行逻辑可能抛出此类异常,必须通过catch块捕获并处理,或通过throws关键字在方法签名中声明抛出,否则代码无法通过编译。
- 编译期强制约束:编译器强制要求显式处理的异常,无任何显式处理逻辑时,程序会直接编译失败;
- 本质原因:通常由程序外部环境异常导致(非编程逻辑错误),具备可恢复性(如重试操作、提示用户修正输入等);
- 典型示例:
IOException:文件 / 网络 IO 操作异常(如读取不存在的文件、网络连接断开);
SQLException:数据库操作异常(如连接超时、SQL 语法错误);
ClassNotFoundException:反射加载类时找不到类定义(如依赖包缺失);
InterruptedException:线程执行阻塞操作时被中断(如 Thread.sleep() 时线程被打断);
ParseException:日期 / 时间解析的专用受检异常,SimpleDateFormat 解析非法格式的日期字符串。
非受检异常(Unchecked Exception)是指 java.lang.RuntimeException 及其所有子类,编译期编译器不强制要求显式处理(即使不捕获 / 声明,代码仍可编译),但运行时触发后若未处理,仍会导致程序崩溃。
- 本质原因:通常由编程逻辑错误导致(需修复代码而非仅处理异常),无恢复性;
- 典型示例:
NullPointerException:调用 null 对象的方法 / 属性(最常见的编程错误);
ArrayIndexOutOfBoundsException:数组下标超出有效范围;
ArithmeticException:算术运算错误(如整数除以 0);
IllegalArgumentException:方法入参不符合业务 / 语法规则;
NumberFormatException:字符串转数值类型失败(如 Integer.parseInt("abc"))。
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出
java处理受检异常的方式
受检异常的核心处理原则是编译期必须显式处理,具体分为两类核心方式:
- 声明抛出:通过 throws 关键字在方法签名中声明该方法可能抛出的受检异常,将异常处理责任转移给方法的调用者;
- 捕获处理:通过 try-catch(可选搭配 finally)捕获异常并在代码块内完成处理,无需将异常传递给上层。
下面对两种方式进行详细讲解。
throws
throws 是 Java 异常处理中声明抛出异常的关键字,用于标注在方法签名后:
表示当前方法不捕获处理异常(仅声明风险),明确告知调用者 "此方法执行过程中可能抛出指定类型的异常";
声明的异常类型主要针对受检异常(非受检异常可声明但编译器不强制),将异常的处理责任转移给方法的调用者(调用者需通过catch处理或继续throws声明);
语法上可声明多个异常类型,用逗号分隔(如throws IOException, SQLException)。

我们看打印信息:
Exception in thread "main" java.lang.ClassNotFoundException: com.qcpy.proxy.sss
以及最后的
at com.qcby.proxy.Test.find(Test.java:9) 和 at com.qcby.proxy.Test.main(Test.java:5)
发现在 find() 中发生了ClassNotFoundException,但该方法并不打算处理该异常,抛出异常至其调用者 main(),main() 处理异常抛出ClassNotFoundException
try-catch-finally
try-catch-finally 是 Java 异常处理中捕获并处理异常的核心语法,用于在方法内部直接处理异常,核心规则如下:
try 块:包裹可能抛出异常的代码逻辑,程序会优先执行 try 块中的代码;只有当 try 块内抛出了 catch 块能匹配的异常类型时,才会触发对应 catch 块的执行(无异常时 catch 块完全跳过);
catch 块:用于捕获并处理 try 块抛出的特定类型异常,只有异常被成功捕获后,程序才会继续执行 catch 块之后的代码;若异常未被任何 catch 块匹配,异常会向上传递,catch 块后续代码不再执行;
finally 块:用于执行无论异常是否发生都需执行的兜底逻辑(如释放资源),几乎一定会执行;
语法规则:
try 块不能单独使用,必须搭配 catch、finally 或两者;
可组合为 try...catch、try...catch...finally、try...finally 三种合法结构;
catch 块可声明一个或多个(子类异常需放在父类异常前),finally 块最多声明一个。
下面我们来用代码演示解决异常的实现效果:

打印结果有横线输出,即执行了catch代码块,但是仍有报错打印,是因为catch代码块中 e.printStackTrace(); 打印报错信息,会将异常的类型、消息、以及堆栈跟踪信息输出至控制台
此时我们这样更改代码:

尝试执行try代码块不成功,我们看打印信息,没有横线,即也没运行catch代码块,原因是catch只能解决ClassNotFoundException的问题,而该问题是ArrayIndexOutOfBoundsException,没办法运行该catch代码块,所以输出的是try无法运行成功的报错内容
如何解决:此时我们再追加catch,实现多级catch

可以解决
由于所有异常的都是Exception类的子类,所以使用Exception接收也可以解决问题

无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会得到执行,如图。

通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等。
注意点:
- 代码块变量作用域规则:
try、catch、finally 三个代码块属于相互独立的局部作用域,遵循 Java 的局部变量作用域规则,在某一个块内定义的局部变量,其作用域仅限于当前块内部,无法跨块访问,若需要在 try、catch、finally 中共享变量,需将变量声明在该结构外部。 - 多 catch 块的匹配与执行规则:
当存在多个 catch 块时,程序会按从上到下的顺序匹配 try 块抛出的异常类型,仅会匹配第一个符合条件的 catch 块(即异常类型与 catch 声明的类型完全一致,或异常是 catch 声明类型的子类),且仅执行该 catch 块的代码,后续所有 catch 块不再匹配、也不会执行;若所有 catch 块的异常类型均无法匹配抛出的异常,该异常会向上传递给方法的调用者处理,此时所有 catch 块都不会执行。 - 多 catch 块的异常类型顺序规则
当 catch 块同时包含子类异常和父类异常(如 NullPointerException 与 Exception)时,必须将子类异常的 catch 块放在父类异常之前。Java 中父类异常的 catch 块会匹配其所有子类异常,若父类异常的 catch 块在前,会优先捕获所有子类异常,导致后续子类异常的 catch 块永远无法被执行(编译可能触发警告,或直接失效)。
下面进行逐个进行演示:
在 Java 中,try、catch、finally 三个代码块的变量作用域是相互独立的,这意味着在其中一个块内定义的变量,只能在当前块内被访问,无法在其他块中直接使用,这是由 Java 的作用域规则决定的。

先捕获具体的子类异常,再捕获其父类异常,确保每种异常都能被正确处理:

如果先捕获Exception父类异常再捕获NullPointerException其子类异常,编译器会直接报错,因为父类异常的catch块会 "吞噬" 所有子类异常,导致子类异常的catch块永远无法执行。
编译错误提示:Exception java.lang.NullPointerException has already been caught

自定义异常
自定义异常是开发者针对特定业务场景自定义的异常类,必须继承 java.lang.Exception(受检)或 java.lang.RuntimeException(非受检)(这是 Java 语法的强制要求,否则无法作为异常类使用)。
通过自定义异常,可脱离 Java 内置异常的通用语义,实现更精准的业务错误分类(如UserNotExistException、OrderTimeoutException)和针对性处理;同时,自定义异常可携带业务上下文信息,让错误原因更清晰,最终提升代码的可读性、可维护性,也便于问题定位。
throw 关键字用于在程序中手动触发异常,其核心规则:
必须抛出 java.lang.Throwable 子类的实例对象(需通过new关键字创建,而非直接抛出异常类本身);例如 throw new NullPointerException(),也可抛出自定义异常实例(如 throw new UserNotExistException("用户ID:1001 不存在"))。
执行 throw 语句后,该语句之后的当前方法代码会立即终止执行,异常对象会沿方法调用栈向上传递:若被上层 try-catch 块捕获,则按 catch 逻辑处理;若未被任何 try-catch 捕获,异常会持续向上传递,最终导致 JVM 终止程序并打印堆栈轨迹。
代码举例:
