一、什么是异常
1. 概念
异常,就是程序运行过程中出现的非正常情况。 当代码出现错误,JVM 会创建异常对象,封装错误信息,然后终止程序。
注意:语法错误不属于异常!语法错误在编译阶段就无法通过,根本不能运行;异常是代码编译成功,运行的时候才发生的错误。
2. 异常体系总览
Java 中所有异常 / 错误的顶层父类:java.lang.Throwable
Error:严重错误,无法处理,程序直接崩溃。比如内存溢出 OOM、栈溢出 StackOverflowError,程序员不能干预。Exception:异常,是我们需要处理的,分为两大类:- 编译时异常(受检异常 CheckedException)
- 运行时异常(非受检异常 RuntimeException)
二、运行时异常与编译时异常
1. 运行时异常 RuntimeException
- 发生时机:程序运行的时候才抛出
- 编译器不强制要求处理,不写 try-catch 或者 throws 也能编译通过
- 大多是代码逻辑问题,写代码时规避即可
- 常见例子:
NullPointerException空指针异常ArrayIndexOutOfBoundsException数组下标越界ArithmeticException算术异常(除 0)ClassCastException类型转换异常
public class Demo { public static void main(String[] args) { int a = 10; int b = 0; //运行时异常:ArithmeticException,编译不会报错,运行才崩溃 System.out.println(a / b); } }2. 编译时异常(受检异常)
- 发生时机:编译阶段编译器就会检查
- 编译器强制要求处理,不处理代码直接编译报错,无法运行
- 大多是外部环境问题,不是代码逻辑 bug,例如文件找不到、IO 读取失败
- 常见例子:
IOException、FileNotFoundException
import java.io.FileReader; public class Demo { public static void main(String[] args) { //FileNotFoundException属于编译时异常,编译器强制要求处理 FileReader fr = new FileReader("test.txt"); } }上面这段代码直接报红,编译失败,必须用
try-catch捕获或者throws抛出异常。
对比表格
| 类型 | 父类 | 编译是否强制处理 | 产生原因 |
|---|---|---|---|
| 运行时异常 | RuntimeException | ❌ 不强制 | 代码逻辑错误,比如空指针、下标越界 |
| 编译时异常 | Exception(非 Runtime) | ✅ 强制处理 | 外部资源问题:文件、网络、IO 等 |
三、异常的产生及处理
1. 异常产生的底层流程
- 代码执行出错,JVM 自动创建对应异常对象,包含异常类型、错误信息、栈追踪信息
- JVM 把异常抛出,传递给调用者
- 如果调用者没有处理,异常继续向上传递,直到 main 方法
- main 方法也不处理:JVM 打印异常栈信息,终止整个程序
默认情况:异常一旦抛出,后面代码不再执行。
2. 异常处理两大方案
方案① try-catch-finally 捕获异常(自己处理)
语法格式:
try{ // 可能出现异常的代码 }catch(异常类型 e){ // 捕获到对应异常后,执行的处理逻辑 }finally{ // 无论是否发生异常,**一定会执行**的代码(资源释放) }代码示例:
public class ExceptionDemo { public static void main(String[] args) { int a = 10; int b = 0; try{ //可能抛出异常的代码 System.out.println(a / b); System.out.println("try中异常后面的代码,异常发生不会执行"); }catch (ArithmeticException e){ //捕获算术异常,在这里处理 System.out.println("捕获到除零异常!"); //打印异常详细信息,调试使用 e.printStackTrace(); }finally { System.out.println("finally代码块,无论有没有异常都会执行"); } //捕获异常后,程序不会崩溃,可以继续往下执行 System.out.println("main方法继续运行"); } }finally 典型用途:关闭 IO 流、数据库连接等资源。 注意:唯一让 finally 不执行的情况:代码执行
System.exit(0);退出 JVM。
方案② throws 声明抛出异常(交给调用者处理)
写在方法签名后,告诉调用者:这个方法有可能抛出异常,调用者需要处理。
注意:throws 只是声明异常,不会处理异常,异常继续向上传递。
import java.io.FileReader; import java.io.IOException; public class Demo { //throws声明,抛出编译时异常IOException,交给调用main方法处理 public static void readFile() throws IOException { FileReader fr = new FileReader("test.txt"); } public static void main(String[] args) throws IOException { readFile(); } }3. 多异常捕获写法
try{ //可能产生多种异常 }catch(NullPointerException e){ System.out.println("空指针"); }catch(ArrayIndexOutOfBoundsException e){ System.out.println("数组越界"); } //JDK7新特性:同类型处理可以合并捕获 catch(ArithmeticException | ClassCastException e){ System.out.println("算术或者类型转换异常"); }捕获多个异常时,父类异常要写在子类异常后面,否则编译报错。
四、throw 手动抛出异常
主动创建异常对象,手动抛出。
throws:写在方法上,声明异常;throw:方法内部,抛出异常对象。
public class Demo { public static void checkAge(int age){ if(age < 0 || age >120){ //手动创建异常对象抛出 throw new RuntimeException("年龄非法!"); } System.out.println("年龄合法"); } public static void main(String[] args) { checkAge(-10); } }五、高频易错点
- 运行时异常编译器不强制捕获,但是一旦发生,程序依然会终止;
- try 中代码出现异常,try 内异常之后的代码不再执行;
- finally 一定会执行,除非 JVM 退出(System.exit);
- throws 仅仅是声明,没有处理异常;throw 才是抛出异常;
- 捕获多异常,父类异常不能写在前面,会把子类异常全部拦截。
六、核心总结
- Throwable 分为 Error(无法处理)和 Exception(需要处理)
- Exception 分编译时异常(强制处理)、运行时异常(不强制)
- 处理异常两种方式:try-catch 捕获,throws 抛出给上层调用者
- finally 用于资源释放,必定执行
- throw 手动抛出异常对象