发布于2026-07-27 阅读(0)
扫一扫,手机访问
在Ja va中,异常处理是程序设计中绕不开的核心机制之一。它专门用来应对程序运行过程中可能出现的各种意外情况。Ja va的异常处理体系主要围绕五个关键点展开:异常类型、异常的抛出、异常的捕获、自定义异常以及finally块的使用。下面就来系统梳理一下,看完之后你会对如何编写更健壮的代码有更清晰的认识。

写Ja va程序的时候,理想情况当然是个好东西,但现实往往不按剧本来。编译阶段一切顺利,可一运行起来,各种问题就冒出来了,比如:
null这些都属于程序运行过程中的异常情况。如果程序员对这些异常完全视而不见,那后果就是程序直接报错终止,甚至影响整个系统的稳定性——用户体验?那更是无从谈起。
所以说,Ja va里的异常知识绝不是“可有可无”的附加内容,而是写出健壮程序的必修课。一个真正能用的程序,不能只考虑“正常情况下怎么跑”,还必须想清楚:
Ja va之所以在企业开发中这么受欢迎,一个很重要的原因就是它对异常机制做了非常完整的设计。基于 Throwable 体系,Ja va把运行中的问题分门别类,并提供了 try、catch、finally、throw、throws 这一整套处理异常的语法工具。
可以这样理解:
异常机制的作用,不是让程序永远不出错,而是让程序在出错时仍然可控。
接下来,我们就围绕Ja va异常机制进行一次系统性的梳理。从概念、分类、语法、执行流程,到常见异常、处理方式、自定义异常、开发规范,尽量把各个角度都讲明白。学习这一章,不能只记住几个关键字,更重要的是理解异常的本质、传播方式以及处理思路。
通过本章学习,可以掌握:
Throwable、Error、Exception 之间的关系try-catch-finally 的基本用法catch 的匹配顺序finally 的作用throw 和 throws 的区别异常,英文叫 Exception。在Ja va中,异常指的就是:程序在运行过程中间出现的不正常情况。这里要特别注意两个关键词:运行过程中 和 不正常情况。也就是说,异常通常不是语法错误,而是程序在执行时遇到了超出预期的问题。
举个例子:
int a = 10; int b = 0; System.out.println(a / b);
这段代码在编译时完全可以通过,因为语法没问题。但一运行就会报错,因为0不能做除数。这就是典型的异常。
很多初学者容易把“编译错误”和“异常”混为一谈。比如下面这个:
int a = "hello";
这不是异常,而是编译错误。因为代码在编译阶段就已经不合法,程序根本无法运行。而下面这个:
String str = null; System.out.println(str.length());
编译没问题,但运行时会出现 NullPointerException。这才属于异常。
所以可以这样区分:
如果没有异常机制,程序一旦出错,通常只能直接崩溃,没有明确提示,调用者也不知道哪里出了问题,程序员也难以定位原因。有了异常机制之后,我们可以:捕获错误、给出清晰信息、必要时中断执行、必要时继续执行后续逻辑、统一处理错误情况。所以,异常机制本质上是在帮助程序员管理运行时风险。
写程序时,通常都会先考虑一种最理想的情况:用户输入正确、文件一定存在、网络一定正常、数据格式一定符合要求……这叫正常流程。但真实世界常常会有意外:用户输错数据、磁盘文件被删掉、数据库连接失败、服务器超时、某个对象本应有值但实际是 null。这些不在正常流程里的情况,就属于异常流程。
可以把异常处理理解成:提前给程序准备“出问题时的备用方案”。比如:文件打不开,就提示“文件不存在”;用户输入错误,就提示“请输入合法数字”;网络断开,就提示“连接失败,请稍后重试”。这就是异常机制在程序设计中的现实意义。
在Ja va中,所有可以被“抛出”的问题对象,最终都继承自 Throwable。也就是说,Throwable 是Ja va异常体系的根类。它下面主要分为两大类:Error 和 Exception。
结构可以简单画成:
Throwable ├── Error └── Exception
Error 表示比较严重的问题,通常不是程序员通过普通代码就能轻易处理的。例如:StackOverflowError(栈溢出)、OutOfMemoryError(内存溢出)。这些问题通常说明程序本身存在严重缺陷,或者运行环境资源出现了问题。在一般开发中,Error 通常不作为常规异常处理对象。
Exception 才是我们平时编程中最常处理的一类异常。例如:数组越界、空指针、文件不存在、类型转换错误、输入输出失败等等。这些异常大多数都和程序逻辑、输入数据、外部资源有关。
在Ja va中,Exception 又可以进一步分成两大类:编译时异常和运行时异常。
编译时异常也叫受查异常(Checked Exception)。这类异常的特点是:编译器要求程序员必须显式处理,否则代码不能通过编译。例如:
FileInputStream fis = new FileInputStream("a.txt");
这里可能会出现文件找不到的问题,所以编译器会要求你:要么 try-catch,要么 throws,否则编译报错。常见的编译时异常有:IOException、FileNotFoundException、SQLException、ClassNotFoundException 等。
运行时异常也叫非受查异常,通常是 RuntimeException 及其子类。这类异常的特点是:编译器不强制要求处理,但运行时可能出问题。例如:
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
编译器不会阻止你,因为语法合法。但运行时会抛出 ArrayIndexOutOfBoundsException。常见的运行时异常有:NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException、ClassCastException、NumberFormatException 等。
这一部分是学习异常时必须重点掌握的内容,因为这些异常在平时做题和写程序时最常见。
空指针异常。示例:
String str = null; System.out.println(str.length());
原因:str 没有真正指向对象,却调用了对象的方法。这是Ja va初学阶段最常见的异常之一。
算术异常。示例:
int a = 10; int b = 0; System.out.println(a / b);
原因:整数除以0。
数组下标越界异常。示例:
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
原因:访问了数组有效范围之外的下标。
字符串下标越界异常。示例:
String s = "abc"; System.out.println(s.charAt(5));
类型转换异常。示例:
Object obj = "hello"; Integer num = (Integer) obj;
原因:实际类型和强制转换目标类型不匹配。
数字格式异常。示例:
String s = "abc"; int num = Integer.parseInt(s);
原因:字符串内容不能转换为数字。
输入输出异常,是很多IO类操作中常见的父异常类型。例如:读文件失败、写文件失败、流关闭失败等。
文件未找到异常。示例:
import ja va.io.FileInputStream;
public class Test {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("abc.txt");
}
}
如果文件不存在,就会出现该异常。
数据库操作异常。例如:SQL语句错误、数据库连接失败、查询执行异常等。
类未找到异常。常出现在反射机制或动态加载类的场景中。
如果异常出现后没有被处理,程序通常会停止执行,在控制台输出异常信息,给出异常类型和出错位置。例如:
public class Test {
public static void main(String[] args) {
int a = 10 / 0;
System.out.println("这句话不会执行");
}
}
运行后:异常被抛出,程序终止,后面的输出语句不会执行。
异常信息通常包括:异常类型、错误原因说明、调用栈信息。例如:
Exception in thread "main" ja va.lang.ArithmeticException: / by zero
at Test.main(Test.ja va:3)
这说明异常类型是 ArithmeticException,原因是 / by zero,错误发生在 Test.ja va 第3行。
try-catch 用来捕获并处理异常。语法格式:
try {
// 可能发生异常的代码
} catch (异常类型 变量名) {
// 异常处理代码
}
public class Test {
public static void main(String[] args) {
try {
int a = 10 / 0;
System.out.println("try 中的后续语句");
} catch (ArithmeticException e) {
System.out.println("出现了算术异常");
}
System.out.println("程序继续执行");
}
}
输出结果说明:10 / 0 出现异常,try 中异常后面的代码不再执行,进入 catch,异常被处理后,程序继续执行 catch 后面的代码。
trycatchtrytry 代码停止catchcatch一个 try 后面可以跟多个 catch,用于处理不同类型的异常。示例:
public class Test {
public static void main(String[] args) {
try {
String s = null;
System.out.println(s.length());
} catch (ArithmeticException e) {
System.out.println("算术异常");
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (Exception e) {
System.out.println("其他异常");
}
}
}
异常会按照 catch 的顺序,从上到下匹配。一旦匹配成功,后面的 catch 不再执行。
如果写成:
catch (Exception e) { }
catch (NullPointerException e) { }
这通常是错误的,因为 Exception 是父类,它已经把后面的子类异常全接住了。因此,多个 catch 的顺序通常应该是:子类异常在前,父类异常在后。
finally 用于表示:无论是否发生异常,通常都要执行的代码。语法:
try {
// 可能异常的代码
} catch (异常类型 e) {
// 异常处理
} finally {
// 一般都会执行
}
最常见的用途是资源释放,例如:关闭文件流、关闭数据库连接、关闭网络连接等。因为这些资源不管程序有没有出错,最后通常都应该关闭。
public class Test {
public static void main(String[] args) {
try {
int a = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("捕获到异常");
} finally {
System.out.println("finally 执行");
}
}
}
输出中 finally 仍然会执行。
一般情况下会执行,但并不是绝对的。例如下面情况可能不会执行:程序在执行前被强制终止、调用了 System.exit(0);、进程被操作系统直接杀死。所以更准确地说:在正常JVM执行流程下,finally 通常都会执行。
完整写法:
try {
// 可能发生异常的代码
} catch (异常类型1 e) {
// 处理方式1
} catch (异常类型2 e) {
// 处理方式2
} finally {
// 最终执行代码
}
try-catchtry-finallytry-catch-finallytrycatchfinally也就是说:try 后面必须跟 catch 或 finally,至少一个。
在 catch 中,捕获到的异常其实是一个对象。例如:
catch (Exception e) {
}
这里的 e 就是异常对象。
打印完整异常信息和调用栈。这是调试时最常用的方法之一。
e.printStackTrace();
获取异常描述信息。
System.out.println(e.getMessage());
返回异常类名和异常信息。
System.out.println(e.toString());
try {
int a = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();
}
throw 用于手动抛出一个异常对象。语法:
throw new 异常类名("异常信息");
public class Test {
public static void main(String[] args) {
int age = -1;
if (age < 0) {
throw new RuntimeException("年龄不能为负数");
}
}
}
这表示程序员主动认为这里是非法情况,于是主动抛出异常。
当程序发现某些条件不符合要求时,可以主动中断流程并抛出异常。例如:参数非法、数据状态不正确、业务条件不满足等。
throws 用于写在方法声明后面,表示:当前方法可能抛出哪些异常,交给调用者处理。语法:
返回值类型 方法名(参数列表) throws 异常类名1, 异常类名2 {
}
import ja va.io.FileInputStream;
import ja va.io.FileNotFoundException;
public class Test {
public static void readFile() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("a.txt");
}
}
这表示:readFile() 方法内部可能抛出 FileNotFoundException,但当前方法自己不处理,交给调用该方法的人去处理。
这是常考点。
在继承中,子类重写父类方法时,也会涉及异常规则。
子类重写父类方法时,抛出的检查异常不能比父类更大。也就是说,子类不能随意扩大异常范围。
因为父类引用调用方法时,编译器是按照父类方法声明来判断异常的。如果子类突然抛出一个更大的受查异常,会破坏原有约定。
可以先记一句:重写方法时,子类抛出的受查异常范围不能超过父类。
很多时候,Ja va自带的异常类并不能完全表达我们的业务含义,这时就可以自定义异常类。
例如系统中有这样的业务规则:用户年龄不能小于0、注册时用户名不能为空、转账金额必须大于0。如果全部都直接抛 throw new RuntimeException("出错了");,虽然也能用,但语义不清晰。如果自定义异常类,含义会更明确。
通常可以继承 Exception(表示受查异常)或 RuntimeException(表示运行时异常)。
class AgeIllegalException extends RuntimeException {
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
使用:
public class Test {
public static void main(String[] args) {
int age = -1;
if (age < 0) {
throw new AgeIllegalException("年龄不能小于 0");
}
}
}
这样程序的异常含义就更明确了。
class LoginException extends Exception {
public LoginException() {
}
public LoginException(String message) {
super(message);
}
}
方法声明中:
public void login(String password) throws LoginException {
if (!"123456".equals(password)) {
throw new LoginException("密码错误");
}
}
这是一个比较经典、也比较容易出错的点。
public class Test {
public static int test() {
try {
return 10;
} finally {
System.out.println("finally 执行");
}
}
}
结果:finally 还是会执行。
因为 return 并不是一写出来就立刻彻底结束。在方法真正返回前,JVM还会去执行 finally。
public class Test {
public static int test() {
try {
return 10;
} finally {
return 20;
}
}
}
最终返回值是20。所以:不建议在 finally 中写 return,因为这样会让程序逻辑变得很混乱,也可能掩盖原本的异常或返回结果。
在早期写法中,IO流通常这样处理:
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这种写法很安全,但比较繁琐。从JDK7开始,可以使用 try(资源对象声明) 语法:
import ja va.io.FileInputStream;
public class Test {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("a.txt")) {
System.out.println("文件打开成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
资源对象需要实现 AutoCloseable 接口,很多流类已经实现了。
例如:catch (Exception e) {},这叫“吞异常”。程序看似没报错,但真实问题被隐藏了,后续非常难排查。
虽然这样写不一定错,但如果不加区分地全部捕获,会让异常处理过于粗糙。很多时候更应该优先捕获更具体的异常类型。
例如明明应该先判断空值,却直接用 try-catch 去包住空指针。这种写法不优雅,也说明代码逻辑前置校验没做好。
finally 本来应该用于收尾、释放资源、执行必要清理,不适合写太复杂的业务逻辑。
如果异常名和异常信息写得很含糊,例如 MyException 或 "出错了",那即使自定义了,也没有实际价值。异常类名和异常提示应该尽量表达真实问题。
这是实际开发中很重要的一个问题。
例如:用户输入错误,可以在当前界面提示重新输入;文件路径错误,可以在当前层提示用户重新选择。这类问题当前层能消化,就可以直接处理。
例如底层工具方法只负责读取文件,它自己并不知道出错后是重试、还是提示用户、还是终止流程。这时它更适合把异常抛给上层,让更高层决定如何处理。
可以记住一句:谁更清楚如何处理这个问题,谁就应该处理异常。
一个优秀程序的重要特点之一,就是“健壮”。所谓健壮,指的是:面对异常输入不轻易崩溃、能给出合理提示、能保留必要日志、能保证重要资源被正确释放。异常机制正是实现程序健壮性的重要基础。在开发中,表单提交要校验数据、文件上传要校验格式、读取配置要考虑文件缺失、网络请求要考虑超时和失败,这些都需要异常思维,而不是只写“理想路径”。
throw:真正抛异常对象throws:声明可能抛出异常类型这是三个完全不同的词。
Object 类中的方法,和垃圾回收相关这三者千万不要混淆。
建议自己动手写下面这些小例子:除0异常、数组越界异常、空指针异常、类型转换异常、文件找不到异常。亲自看到异常信息,比单纯看书更容易记住。
例如:不加 try-catch 时程序怎样,加了 try-catch 后程序怎样。这样才能真正理解异常机制的作用。
异常这章最容易出错的地方,不是概念,而是流程。一定要弄清:异常在哪里产生、try 中后面代码还会不会执行、会进入哪个 catch、finally 会不会执行、方法到底是处理了还是抛出了异常。
学习顺序建议:
try-catch-finallythrow / throws这样更顺。
异常表示程序在运行过程中间出现的不正常情况。它不是单纯的“报错信息”,而是一种完整的错误处理机制。
Ja va异常体系顶层是 Throwable,下面分为 Error 和 Exception。
Exception 主要分为编译时异常和运行时异常。其中,编译时异常要求强制处理,运行时异常通常反映代码逻辑问题。
最常见的语法有:try、catch、finally、throw、throws。
throw:抛出具体异常对象throws:声明方法可能抛出的异常类型Ja va中的异常机制,是连接“程序能运行”和“程序能稳定运行”之间的一座桥梁。很多初学者刚开始学异常时,会觉得它只是几个关键字和一些报错信息,但实际上,异常体现的是一种更完整的编程思维:不只写正常情况,还要考虑错误情况;不只让代码跑起来,还要让代码在出问题时保持可控。真正优秀的程序,并不是从不出错,而是在出错时仍然能够给出明确信息、保证资源安全、降低故障影响、方便后续排查。因此,学习异常这一章时,不能只把它当成一个知识点,而要把它看成Ja va程序设计中非常关键的一种“防御性思维”。只有真正掌握了异常机制,后面学习IO、集合、多线程、数据库、网络编程和项目开发时,代码才会更加规范,也更加稳定。
try {
// 可能出异常的代码
} catch (Exception e) {
e.printStackTrace();
}
try {
// 业务代码
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("收尾操作");
}
throw new RuntimeException("参数非法");
public void test() throws IOException {
}
class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
try (FileInputStream fis = new FileInputStream("a.txt")) {
// 使用资源
}
到此,Ja va中所有的语法部分已经介绍完毕,下一章我们将介绍Ja va中的常用类。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8