在java中,异常处理是一种非常重要且常用的机制,用于处理程序中可能发生的错误情况。java异常处理机制主要包括五个关键点:异常类型、异常的抛出、异常的捕获、自定义异常以及finally块的使用。以下是详细的介绍和示例:掌握java异常机制,编写更健壮的代码!本文带你系统学习异常类型、try-catch-finally、throw和throws用法,用主动有力的讲解助你理解编译时与运行时异常的区别,立即点击,学会在出错时控制程序流程,避免程序崩溃,提升代码稳定性和排查问题的能力
前言
在编写 java 程序的过程中,程序并不总是会按照我们的理想情况顺利运行。很多时候,代码在编译阶段看起来没有问题,但在运行阶段却可能因为各种原因出错,例如:
- 除数不能为 0
- 数组下标越界
- 字符串为
null - 文件找不到
- 输入的数据格式不正确
- 网络连接失败
- 类型转换失败
这些问题,本质上都属于程序运行过程中的异常情况。
如果程序员对这些异常情况完全不做处理,那么轻则程序报错终止,重则影响整个系统的稳定性和用户体验。
因此,在 java 里,异常并不是一个“可有可无”的附加知识,而是编写健壮程序必须掌握的内容。一个真正可用的程序,不能只考虑“正常情况下怎么运行”,还必须考虑:
- 出现错误时怎么办
- 错误发生后要不要继续执行
- 如何给调用者一个明确提示
- 如何避免程序直接崩溃
- 怎样把错误信息保留下来,方便排查问题
java 之所以在企业开发中被广泛使用,其中一个重要原因,就是它对异常机制做了比较完整的设计。java 通过 throwable 体系,把运行中出现的问题进行了分类,并且提供了:
trycatchfinallythrowthrows
等一整套处理异常的语法工具。
可以这样理解:
异常机制的作用,不是让程序永远不出错,而是让程序在出错时仍然可控。
本章将围绕 java 中的异常机制进行系统讲解,尽量从概念、分类、语法、执行流程、常见异常、处理方式、自定义异常、开发规范等多个角度展开说明。
学习这一章时,不能只记住几个关键字,更重要的是要理解异常的本质、传播方式和处理思路。
通过本章学习,可以掌握:
- 什么是异常
- 异常与普通错误提示的区别
- java 异常体系结构
throwable、error、exception之间的关系- 编译时异常和运行时异常的区别
try-catch-finally的基本用法- 多个
catch的匹配顺序 finally的作用throw和throws的区别- 如何自定义异常类
- 常见异常类型及触发场景
- 异常处理中常见错误与开发建议
一、什么是异常
1. 异常的基本概念
异常,英文叫 exception。在 java 中,异常指的是:
程序在运行过程中出现的不正常情况。
这里要特别注意两个词:
- 运行过程中
- 不正常情况
也就是说,异常通常不是语法错误,而是程序在执行时遇到了超出预期的问题。
例如:
int a = 10; int b = 0; system.out.println(a / b);
这段代码在编译时是可以通过的,因为语法没有问题。
但运行时会报错,因为 0 不能作为除数。
这就是异常。
2. 异常和语法错误的区别
很多初学者容易把“编译错误”和“异常”混在一起。
例如下面这段代码:
int a = "hello";
这不是异常,而是编译错误。
因为代码在编译阶段就已经不合法,程序根本无法运行。
而下面这段:
string str = null; system.out.println(str.length());
编译没问题,但运行时会出现 nullpointerexception。
这才属于异常。
所以可以这样区分:
编译错误
- 语法、类型、结构不合法
- 程序连运行机会都没有
异常
- 编译可以通过
- 运行时出现问题
3. 为什么程序需要异常机制
如果没有异常机制,程序一旦出错,通常只能:
- 直接崩溃
- 没有明确提示
- 调用者不知道哪里出问题
- 程序员也难以定位原因
有了异常机制后,可以做到:
- 捕获错误
- 给出清晰信息
- 必要时中断执行
- 必要时继续执行后续逻辑
- 统一处理错误情况
因此,异常机制本质上是在帮助程序员管理运行时风险。
二、生活化理解异常
1. 正常流程和异常流程
写程序时,通常都会先考虑一种最理想的情况:
- 用户输入正确
- 文件一定存在
- 网络一定正常
- 数据格式一定符合要求
这种情况叫正常流程。
但真实世界中常常会有一些意外:
- 用户输错数据
- 磁盘文件被删掉了
- 数据库连接失败
- 服务器超时
- 某个对象本来应该有值,但实际上是
null
这些不在正常流程里的情况,就属于异常流程。
2. 异常处理的意义
可以把异常处理理解成:
提前给程序准备“出问题时的备用方案”。
例如:
- 文件打不开,就提示“文件不存在”
- 用户输入错误,就提示“请输入合法数字”
- 网络断开,就提示“连接失败,请稍后重试”
这就是异常机制在程序设计中的现实意义。
三、java 中的异常体系
1. 所有异常和错误的顶层父类:throwable
在 java 中,所有可以被“抛出”的问题对象,最终都继承自 throwable。
也就是说:
throwable是 java 异常体系的根类。
它下面主要分为两大类:
errorexception
可以简单画成:
throwable ├── error └── exception
2.error
error 表示比较严重的问题,通常不是程序员通过普通代码就能轻易处理的。
例如:
stackoverflowerror:栈溢出outofmemoryerror:内存溢出
这些问题通常说明:
- 程序本身存在严重缺陷
- 或运行环境资源出现问题
在一般开发中,error 通常不作为常规异常处理对象。
3.exception
exception 才是我们平时编程中最常处理的一类异常。
例如:
- 数组越界
- 空指针
- 文件不存在
- 类型转换错误
- 输入输出失败
这些异常大多数都和程序逻辑、输入数据、外部资源有关。
四、异常的分类
在 java 中,exception 又可以进一步分成两大类:
- 编译时异常
- 运行时异常
1. 编译时异常
编译时异常也叫受查异常,英文常叫:
- checked exception
这类异常的特点是:
编译器要求程序员必须显式处理,否则代码不能通过编译。
例如:
fileinputstream fis = new fileinputstream("a.txt");
这里可能会出现文件找不到的问题,所以编译器会要求你:
- 要么
try-catch - 要么
throws
否则编译报错。
常见编译时异常有:
ioexceptionfilenotfoundexceptionsqlexceptionclassnotfoundexception
2. 运行时异常
运行时异常也叫非受查异常,通常是:
runtimeexception及其子类
这类异常的特点是:
编译器不强制要求处理,但运行时可能出问题。
例如:
int[] arr = {1, 2, 3};
system.out.println(arr[5]);
编译器不会阻止你,因为语法合法。
但运行时会抛出:
arrayindexoutofboundsexception
常见运行时异常有:
nullpointerexceptionarithmeticexceptionarrayindexoutofboundsexceptionclasscastexceptionnumberformatexception
3. 二者区别总结
编译时异常
- 编译器强制处理
- 不处理就无法通过编译
- 通常和外部资源、环境相关
运行时异常
- 编译器不强制处理
- 运行时才会暴露问题
- 通常和代码逻辑错误有关
五、常见运行时异常
这一部分是学习异常时必须重点掌握的内容,因为这些异常在平时做题和写程序时最常见。
1.nullpointerexception
空指针异常。
示例:
string str = null; system.out.println(str.length());
原因:
str没有真正指向对象- 却调用了对象的方法
这是 java 初学阶段最常见的异常之一。
2.arithmeticexception
算术异常。
示例:
int a = 10; int b = 0; system.out.println(a / b);
原因:
- 整数除以 0
3.arrayindexoutofboundsexception
数组下标越界异常。
示例:
int[] arr = {1, 2, 3};
system.out.println(arr[3]);
原因:
- 访问了数组有效范围之外的下标
4.stringindexoutofboundsexception
字符串下标越界异常。
示例:
string s = "abc"; system.out.println(s.charat(5));
5.classcastexception
类型转换异常。
示例:
object obj = "hello"; integer num = (integer) obj;
原因:
- 实际类型和强制转换目标类型不匹配
6.numberformatexception
数字格式异常。
示例:
string s = "abc"; int num = integer.parseint(s);
原因:
- 字符串内容不能转换为数字
六、常见编译时异常
1.ioexception
输入输出异常,是很多 io 类操作中常见的父异常类型。
例如:
- 读文件失败
- 写文件失败
- 流关闭失败
2.filenotfoundexception
文件未找到异常。
示例:
import java.io.fileinputstream;
public class test {
public static void main(string[] args) throws exception {
fileinputstream fis = new fileinputstream("abc.txt");
}
}如果文件不存在,就会出现该异常。
3.sqlexception
数据库操作异常。
例如:
- sql 语句错误
- 数据库连接失败
- 查询执行异常
4.classnotfoundexception
类未找到异常。
常出现在反射机制或动态加载类的场景中。
七、异常出现后程序会怎样
1. 默认情况:程序中断
如果异常出现后,没有被处理,那么程序通常会:
- 停止执行
- 在控制台输出异常信息
- 给出异常类型和出错位置
例如:
public class test {
public static void main(string[] args) {
int a = 10 / 0;
system.out.println("这句话不会执行");
}
}运行后:
- 异常被抛出
- 程序终止
- 后面的输出语句不会执行
2. 异常信息一般包括什么
异常信息通常包括:
- 异常类型
- 错误原因说明
- 调用栈信息
例如:
exception in thread "main" java.lang.arithmeticexception: / by zero
at test.main(test.java:3)
这说明:
- 异常类型是
arithmeticexception - 原因是
/ by zero - 错误发生在
test.java第 3 行
八、try-catch异常捕获机制
1. 基本作用
try-catch 用来捕获并处理异常。
语法格式:
try {
// 可能发生异常的代码
} catch (异常类型 变量名) {
// 异常处理代码
}
2. 示例
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后面的代码
3. 执行流程
没有异常
- 执行
try - 跳过
catch - 继续往后执行
有异常
- 执行
try - 发生异常,后续
try代码停止 - 找匹配的
catch - 执行对应
catch - 再继续往后执行
九、多个catch
一个 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("其他异常");
}
}
}1. 匹配原则
异常会按照 catch 的顺序,从上到下匹配。
一旦匹配成功:
- 后面的
catch不再执行
2. 注意顺序
如果写成:
catch (exception e) { }
catch (nullpointerexception e) { }
这通常是错误的,因为:
exception是父类- 它已经把后面的子类异常全接住了
因此,多个 catch 的顺序通常应该是:
子类异常在前,父类异常在后。
十、finally语句块
1. 基本作用
finally 用于表示:
无论是否发生异常,通常都要执行的代码。
语法:
try {
// 可能异常的代码
} catch (异常类型 e) {
// 异常处理
} finally {
// 一般都会执行
}
2. 典型用途
最常见的用途是资源释放,例如:
- 关闭文件流
- 关闭数据库连接
- 关闭网络连接
因为这些资源不管程序有没有出错,最后通常都应该关闭。
3. 示例
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 仍然会执行。
4.finally一定执行吗
一般情况下会执行,但并不是绝对的。
例如下面情况可能不会执行:
- 程序在执行前被强制终止
- 调用了
system.exit(0); - 进程被操作系统直接杀死
所以更准确地说:
在正常 jvm 执行流程下,
finally通常都会执行。
十一、try-catch-finally的完整形式
完整写法:
try {
// 可能发生异常的代码
} catch (异常类型1 e) {
// 处理方式1
} catch (异常类型2 e) {
// 处理方式2
} finally {
// 最终执行代码
}
1. 组合关系
合法形式
try-catchtry-finallytry-catch-finally
非法形式
- 只有
try - 只有
catch - 只有
finally
也就是说:
try后面必须跟catch或finally,至少一个。
十二、异常对象常用方法
在 catch 中,捕获到的异常其实是一个对象。
例如:
catch (exception e) {
}
这里的 e 就是异常对象。
1.printstacktrace()
打印完整异常信息和调用栈。
e.printstacktrace();
这是调试时最常用的方法之一。
2.getmessage()
获取异常描述信息。
system.out.println(e.getmessage());
3.tostring()
返回异常类名和异常信息。
system.out.println(e.tostring());
4. 示例
try {
int a = 10 / 0;
} catch (exception e) {
system.out.println(e.getmessage());
system.out.println(e.tostring());
e.printstacktrace();
}
十三、throw关键字
1. 作用
throw 用于:
手动抛出一个异常对象。
语法:
throw new 异常类名("异常信息");
2. 示例
public class test {
public static void main(string[] args) {
int age = -1;
if (age < 0) {
throw new runtimeexception("年龄不能为负数");
}
}
}
这表示程序员主动认为这里是非法情况,于是主动抛出异常。
3. 使用场景
当程序发现某些条件不符合要求时,可以主动中断流程并抛出异常。
例如:
- 参数非法
- 数据状态不正确
- 业务条件不满足
十四、throws关键字
1. 作用
throws 用于写在方法声明后面,表示:
当前方法可能抛出哪些异常,交给调用者处理。
语法:
返回值类型 方法名(参数列表) throws 异常类名1, 异常类名2 {
}
2. 示例
import java.io.fileinputstream;
import java.io.filenotfoundexception;
public class test {
public static void readfile() throws filenotfoundexception {
fileinputstream fis = new fileinputstream("a.txt");
}
}这表示:
readfile()方法内部可能抛出filenotfoundexception- 但当前方法自己不处理
- 交给调用该方法的人去处理
3.throw和throws的区别
这是常考点。
throw
- 用在方法内部
- 后面跟异常对象
- 表示“抛出一个具体异常”
throws
- 用在方法声明处
- 后面跟异常类型
- 表示“声明可能抛出哪些异常”
十五、方法重写与异常
在继承中,子类重写父类方法时,也会涉及异常规则。
1. 基本原则
子类重写父类方法时:
- 抛出的检查异常不能比父类更大
也就是说,子类不能随意扩大异常范围。
2. 原因
因为父类引用调用方法时,编译器是按照父类方法声明来判断异常的。
如果子类突然抛出一个更大的受查异常,会破坏原有约定。
3. 初学阶段怎么记
可以先记一句:
重写方法时,子类抛出的受查异常范围不能超过父类。
十六、自定义异常
很多时候,java 自带的异常类并不能完全表达我们的业务含义。
这时就可以自定义异常类。
1. 为什么要自定义异常
例如系统中有这样一种业务规则:
- 用户年龄不能小于 0
- 注册时用户名不能为空
- 转账金额必须大于 0
如果全部都直接抛:
throw new runtimeexception("出错了");
虽然也能用,但语义不清晰。
如果自定义异常类,含义会更明确。
2. 自定义异常的方式
通常可以继承:
exceptionruntimeexception
继承exception
表示受查异常。
继承runtimeexception
表示运行时异常。
3. 示例:自定义运行时异常
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");
}
}
}
这样程序的异常含义就更明确了。
4. 示例:自定义受查异常
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("密码错误");
}
}
十七、finally与return的关系
这是一个比较经典、也比较容易出错的点。
1. 先看示例
public class test {
public static int test() {
try {
return 10;
} finally {
system.out.println("finally 执行");
}
}
}
结果:
finally还是会执行
2. 为什么会这样
因为 return 并不是一写出来就立刻彻底结束。
在方法真正返回前,jvm 还会去执行 finally。
3. 更复杂的情况
public class test {
public static int test() {
try {
return 10;
} finally {
return 20;
}
}
}
最终返回值是:
20
说明:
不建议在
finally中写return。
因为这样会让程序逻辑变得很混乱,也可能掩盖原本的异常或返回结果。
十八、jdk7 之后的资源自动关闭
在早期写法中,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 java.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();
}
}
}1. 优点
- 代码更简洁
- 自动关闭资源
- 更不容易遗漏释放操作
2. 前提
资源对象需要实现:
autocloseable
很多流类都已经实现了这个接口。
十九、异常处理中常见错误
1. 抓了异常却什么也不做
例如:
catch (exception e) {
}
这叫“吞异常”。
程序看似没报错,但真实问题被隐藏了,后续非常难排查。
2. 一上来就catch(exception)
虽然这样写不一定错,但如果不加区分地全部捕获,会让异常处理过于粗糙。
很多时候更应该优先捕获更具体的异常类型。
3. 把运行时异常全靠try-catch掩盖
例如明明应该先判断空值,却直接用 try-catch 去包住空指针。
这种写法不优雅,也说明代码逻辑前置校验没做好。
4. 在finally中写复杂逻辑
finally 本来应该用于:
- 收尾
- 释放资源
- 执行必要清理
不适合写太复杂的业务逻辑。
5. 自定义异常时语义不清
如果异常名和异常信息写得很含糊,例如:
myexception"出错了"
那即使自定义了,也没有实际价值。
异常类名和异常提示应该尽量表达真实问题。
二十、什么时候该处理,什么时候该抛出
这是实际开发中很重要的一个问题。
1. 当前方法能解决,就处理
例如:
- 用户输入错误,可以在当前界面提示重新输入
- 文件路径错误,可以在当前层提示用户重新选择
这类问题当前层能消化,就可以直接处理。
2. 当前方法解决不了,就往上抛
例如底层工具方法只负责读取文件,它自己并不知道:
- 出错后是重试
- 还是提示用户
- 还是终止流程
这时它更适合把异常抛给上层,让更高层决定如何处理。
3. 一般原则
可以先记住一句:
谁更清楚如何处理这个问题,谁就应该处理异常。
二十一、异常机制和程序健壮性
一个优秀程序的重要特点之一,就是“健壮”。
所谓健壮,指的是:
- 面对异常输入不轻易崩溃
- 能给出合理提示
- 能保留必要日志
- 能保证重要资源被正确释放
异常机制正是实现程序健壮性的重要基础。
例如在开发中:
- 表单提交要校验数据
- 文件上传要校验格式
- 读取配置要考虑文件缺失
- 网络请求要考虑超时和失败
这些都需要异常思维,而不是只写“理想路径”。
二十二、学习异常时最容易混淆的几个点
1.throw和throws
throw:真正抛异常对象throws:声明可能抛出异常类型
2. 编译时异常和运行时异常
- 编译时异常:编译器强制处理
- 运行时异常:编译器不强制,但运行时会出问题
3.final、finally、finalize
这是三个完全不同的词。
final
- 修饰类、方法、变量
finally
- 异常处理中的最终执行块
finalize
object类中的方法,和垃圾回收相关- 现代开发中基本不作为重点使用
这三者千万不要混淆。
二十三、学习建议
1. 不要只背概念,要亲手制造异常
建议自己动手写下面这些小例子:
- 除 0 异常
- 数组越界异常
- 空指针异常
- 类型转换异常
- 文件找不到异常
亲自看到异常信息,比单纯看书更容易记住。
2. 多比较“处理前”和“处理后”
例如:
- 不加
try-catch时程序怎样 - 加了
try-catch后程序怎样
这样才能真正理解异常机制的作用。
3. 重点抓住执行流程
异常这章最容易出错的地方,不是概念,而是流程。
一定要弄清:
- 异常在哪里产生
try中后面代码还会不会执行- 会进入哪个
catch finally会不会执行- 方法到底是处理了还是抛出了异常
4. 先掌握常见异常,再学自定义异常
学习顺序建议:
- 常见运行时异常
try-catch-finallythrow/throws- 编译时异常
- 自定义异常
这样更顺。
二十四、本章知识总结
1. 异常的本质
异常表示程序在运行过程中出现的不正常情况。
它不是单纯的“报错信息”,而是一种完整的错误处理机制。
2. 异常体系
java 异常体系顶层是:
throwable
下面分为:
errorexception
3. 异常分类
exception 主要分为:
- 编译时异常
- 运行时异常
其中:
- 编译时异常要求强制处理
- 运行时异常通常反映代码逻辑问题
4. 异常处理语法
最常见的语法有:
trycatchfinallythrowthrows
5. 关键区别
throw:抛出具体异常对象throws:声明方法可能抛出的异常类型
6. 开发中的重点
- 不要吞异常
- 能处理就处理,不能处理就抛出
- 优先捕获具体异常
- 注意资源释放
- 必要时自定义异常
二十五、结语
java 中的异常机制,是连接“程序能运行”和“程序能稳定运行”之间的一座桥梁。很多初学者刚开始学异常时,会觉得它只是几个关键字和一些报错信息,但实际上,异常体现的是一种更完整的编程思维:
- 不只写正常情况
- 还要考虑错误情况
- 不只让代码跑起来
- 还要让代码在出问题时保持可控
真正优秀的程序,并不是从不出错,而是在出错时仍然能够:
- 给出明确信息
- 保证资源安全
- 降低故障影响
- 方便后续排查
因此,学习异常这一章时,不能只把它当成一个知识点,而要把它看成 java 程序设计中非常关键的一种“防御性思维”。
只有真正掌握了异常机制,后面学习 io、集合、多线程、数据库、网络编程和项目开发时,代码才会更加规范,也更加稳定。
二十六、附:常见语法速查表
1.try-catch
try {
// 可能出异常的代码
} catch (exception e) {
e.printstacktrace();
}
2.try-catch-finally
try {
// 业务代码
} catch (exception e) {
e.printstacktrace();
} finally {
system.out.println("收尾操作");
}
3.throw
throw new runtimeexception("参数非法");
4.throws
public void test() throws ioexception {
}
5. 自定义异常
class myexception extends runtimeexception {
public myexception(string message) {
super(message);
}
}
6. 自动关闭资源
try (fileinputstream fis = new fileinputstream("a.txt")) {
// 使用资源
}
好的,你们本章就到此结束了,在本章我们主要理解了java当中异常的运行机制。到此为止,java中所有的语法部分也介绍完毕,下一章我们介绍java中的常用类。
到此这篇关于java中的异常的文章就介绍到这了,更多相关java中的异常内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论