‌Java中的异常(Exception)是程序运行时发生的不正常事件,分为检查异常(Checked Exception)和运行时异常(RuntimeException)。‌ 异常处理机制包括捕获(try-catch)、声明(throws)和抛出(throw),确保程序在错误发生时仍能稳定运行或优雅终止。

原文链接:全网最完整Java学习笔记

基本介绍

什么是异常

Java中,异常(Exception)是指在程序执行过程中出现问题的一种情况,它可以中断程序的正常执行。异常通常是指由于错误、非法操作或意外情况导致的问题,比如文件未找到、数组越界、空指针引用等等。

异常机制:

程序出现异常,程序安全的退出、处理完后继续执行的机制。Java是采用面向对象的方式来处理异常的。处理过程:

  • 抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给JRE
  • 捕获异常:JRE得到该异常后,寻找相应的代码来处理该异常。JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止

异常的分类

  • Error:错误类,无法通过代码解决,所以也不能处理。通常是由于系统资源不足或者JVM内部错误等导致的,需要通过修改环境或者配置等方式来解决。例如系统内存不够时抛出的内存溢出错误OutOfMemoryError,递归栈太深时抛出栈溢出错误StackOverflowError,这些通过代码没法解决,需要提升服务器配置,或者完全重构代码,换一种时间、空间复杂度更低的方案。
  • Exception:异常类,程序可以处理的问题。它是一个类的实例,用于表示程序在运行过程中出现的意外情况。Java中所有的异常都是Throwable类的子类。Exception分为两种主要类型:运行时异常(RuntimeException及其子类)和非运行时异常。当一段代码有异常风险时应该通过try-catch或者throws进行处理,防止程序出现问题。例如往数据库插入记录时要捕获并打日志,从而对违反主键约束之类等问题进行排查。
  • **RuntimeException**:运行时异常,编译不出错,运行出错,要try-catch处理。这类异常通常是由程序错误或者逻辑错误导致的,例如空指针引用、数组越界等。由于RuntimeException在编译时不受检查,所以需要在代码编写阶段考虑如何处理这类异常,以确保程序的健壮性。
  • **非RuntimeException**:编译时异常,编译时出错使程序不能运行,要try-catch处理或者throws抛出去。它是在编译时必须处理的异常类型,否则程序无法通过编译。这类异常常表示程序运行环境出现的异常情况,如IO异常IOException、数据库操作异常等。

异常继承体系

image-20250722170324655

1753175232890

详细介绍

Error类

Java中的Error类表示严重的错误情况,通常由虚拟机或其他底层自身的失效造成的,例如内存溢出、栈溢出,会导致应用程序终止。

通常程序不应该捕获Error,特定情境下可以捕获OutOfMemoryError处理内存溢出问题。使用try-catch-finally块捕获异常,并在finally块中进行资源清理、销毁、报告错误、终止应用程序等操作。

常见的错误Error包括:

  • **OutOfMemoryError**:内存溢出错误,通常是由于应用程序试图分配比可用内存更多的内存而导致。
  • **StackOverflowError**:堆栈溢出错误,发生在方法递归调用所需的堆栈空间已经用完的情况下。
  • **NoClassDefFoundError**:类未找到错误,通常是由于JVM无法找到应用程序尝试使用的某个类而导致。
  • **UnsatisfiedLinkError**:链接未满足错误,通常是由于调用本地方法时出现的链接问题而导致。

Exception类

Exception的子类包括编译时异常和运行时异常:

编译时异常:在编译阶段就能检查出来的异常。例如FileNotFoundException、ClassNotFoundException、NoSuchFieldException、NoSuchMethodException、SQLException、ParseException(解析异常)等。如果程序要去处理这些异常,必须显式地使用try-catch语句块或者在方法定义中使用throws子句声明异常。

运行时异常:在运行时才会出现的异常。例如,NullPointerException、ArrayIndexOutOfBoundsException等。这些异常通常是由程序代码中的逻辑错误引起的,在编程时不会提示,运行时才报错。 因此,在编写程序时,通常无法处理这些异常,但是在程序开发完毕后,需要对这些异常进行一些处理,以防程序运行时崩溃。

常见的异常类

  • NullPointerException 空指针异常;出现原因:访问未初始化的对象或不存在的对象。
  • ClassNotFoundException 类找不到异常;出现原因:类的名称和路径加载错误;
  • NumberFormatException 数字格式化异常;出现原因:转数字的字符型中包含非数字型字符。
  • IndexOutOfBoundsException 索引超出边界异常;出现原因:访问数组越界元素
  • IllegalArgumentException 不合法参数异常。出现原因:传递了不合法参数
  • MethodArgumentNotValidException 方法参数无效异常。出现原因:JSR303校验不通过
  • ClassCastException 类转换异常。出现原因:把对象强制转为没继承关系对象时报错。这个异常是在类加载过程的元数据验证阶段验证继承关系时报错。
  • ArithmeticException 算术异常。出现原因:除以0时。
  • FileNotFoundException 文件未找到异常
  • NoSuchMethodException 方法不存在异常
  • IOException IO 异常
  • SocketException Socket 异常

异常的两种处理方式

抛出异常

  • throws
    • 位置:在方法签名中使用,其后跟着异常类名。
    • 特点:它表示方法可能会抛出异常,但并不保证一定会发生这个异常。
    • 数量:可以声明抛出多个异常,多个一场之间用逗号隔开
    • 处理异常:异常会传递给该方法的调用者来处理。
  • throw
    • 位置:在方法体内使用,其后跟着异常对象名。
    • 特点:它表示方法内部一定已经发生了某种异常情况,并将这个异常抛出。
    • 数量:throw语句抛出的是一个异常实例,不是一个异常类,而且每次只能抛出一个异常实例
    • 处理异常:执行该关键字必定会抛出异常。异常由方法体内的语句来处理。
1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
// throws可能会抛出异常
// @throws Exception 抛出异常
public void possibleException() throws Exception {
// 可能会引发空指针异常的代码
}

// throw一定会抛出指定的异常
public void throwException(){
throw new RuntimeException("这里一定会抛出一个运行时异常");
}
}

throw 和 throws 的区别

throw:是真实抛出一个异常。
throws:是声明可能会抛出一个异常。

捕获异常(推荐)

使用 try 和 catch 关键字可以捕获异常,需要将try/catch 包围在异常可能发生的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 2. try/catch语法:
try{
// 可能会引发空指针异常的代码
}catch(Exception e){
// 异常处理代码
}
// 若还有逻辑,异常被捕获、处理后,程序将不被终止,而是继续执行

// 2. try/catch/finally语法:
try{
// 可能会引发空指针异常的代码
}catch(Exception e){
// 异常处理代码
}finally{
// 只要程序不崩溃,不管catch里有没有捕获到异常,finally块中的代码都会在最后执行
}
// 3.可以使用 try-with-resources 语句来自动管理资源,如自动关闭实现了 AutoCloseable 或 Closeable 接口的资源。
try (Resource resource = new Resource()) {
// 使用资源
} catch (Exception e) {
// 处理异常
}

final、finally、finalize 的区别

final:修饰符,如果修饰类,此类不能被继承;如果修饰方法和变量,此方法和此变量不能再被改变,只能使用。
finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何情况都会执行,finally 部分可以省略,但若 finally 部分存在,则一定会执行 finally 里面的代码。
finalize:是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法

try-catch-finally 中哪部分可省

try-catch-finally 其中 catch 和 finally 都可以被省略,但不能同时省略,也就是说有 try 时,必须后面跟一个 catch 或 finally。

try-catch-finally 在 catch 中 return 了,finally 是否还会执行

finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。

finally块最终执行

  • 只要程序不崩溃,finally块的代码都最终执行:即使try块、catch块中有return或throw语句,程序也会在执行finally块的代码后再return或throw。这里需要注意,禁止在finally块中使用return或throw。因为若finally块里也使用了return或throw等语句,finally块会终止方法,系统将不会跳回去执行try块、catch块里的任何代码。这将会导致try块、catch块中的return、throw语句失效
  • 如果程序终止,finally代码块不执行:
    • 线程终止:如果一个线程在执行 try 语句块或者catch语句块时被打断interrupted,或者被终止killed,与其相对应的 finally 语句块可能不会执行。
    • 退出虚拟机:如果在try块或catch块中使用 System.exit(1); 来退出虚拟机,则finally块将失去执行的机会。

自定义异常:继承异常类

有时候预定义的异常类不能完全满足业务需求,这时就需要自定义异常,以便于在程序出现问题时,可以及时抛出或处理

例如电商项目的库存不足异常、商品找不到异常,论坛项目中的“帖子找不到异常”、“无效评论异常”等等。

自定义异常的方法: 可以通过继承异常根类,或者它们的子类,重写父类的方法,以达到自定义异常的效果:

  • 编译时异常类:需要继承 Exception 类。
  • 运行时异常类:需要继承 RuntimeException 类。