You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
10 KiB
10 KiB
成绩平均分计算项目 - 异常处理学习 (Score Average Calculator - Exception Handling)
项目概述
这是一个Java学习项目,重点强调异常处理的必要性。通过对比两个版本的代码展示:
- ❌ 没有异常处理的代码会如何崩溃
- ✅ 使用异常处理的代码如何优雅应对错误
项目结构
Java源代码文件
ScoreAverage0.java ⚠️
- 描述:未进行异常处理的版本(用于演示问题)
- 特点:
- 移除了
throws Exception- 强制必须处理异常 - 没有 try-catch 块
- 直接调用会抛异常的方法:
FileReader()- 可能抛 FileNotFoundExceptionBufferedReader.readLine()- 可能抛 IOExceptionInteger.parseInt()- 可能抛 NumberFormatException
- 会导致程序崩溃 ❌
- 移除了
- 适用场景:
- 理解"如果不处理异常会怎样"
- 强调异常处理的重要性
- 作为负反例学习
ScoreAverage1.java ✅
- 描述:改进版本,具备完整的异常处理
- 特点:
- 使用 try-catch 块捕获三种异常:
NumberFormatException:处理数字格式错误FileNotFoundException:处理文件不存在IOException:处理其他IO错误
- 使用 try-with-resources 自动关闭资源
- 检查成绩数量,防止零数除
- 程序不会崩溃 ✅
- 使用 try-catch 块捕获三种异常:
- 适用场景:生产级代码,具备健壮的错误处理
测试数据文件与异常演示
ScoreAverage0 - 异常演示场景
| 文件名 | 包含内容 | 期望的异常 | 说明 |
|---|---|---|---|
score_invalid_format.txt |
包含 "abc" 等非数字内容 | NumberFormatException |
当调用 Integer.parseInt("abc") 时 |
score.txt |
空文件 | ArithmeticException |
分母为0时,计算平均值异常 |
(不存在的文件如 missing.txt) |
不存在 | FileNotFoundException |
当文件不存在时 |
ScoreAverage1 - 异常处理演示
| 测试场景 | 使用文件 | 结果 | 说明 |
|---|---|---|---|
| 正常情况 | score_valid.txt (85, 92, 78, 88, 95) |
计算出平均分 | 程序正常运行 ✅ |
| 数字格式错误 | score_invalid_format.txt |
捕获异常,提示用户 | 程序不崩溃 ✅ |
| 空文件 | score_empty.txt |
提示"No scores to average." | 程序不崩溃 ✅ |
| 文件不存在 | (手动改为不存在的名称) | 捕获异常,提示用户 | 程序不崩溃 ✅ |
使用方法 - 异常演示步骤
第一步:观察异常的危害(ScoreAverage0.java)
1. 编译代码
javac ScoreAverage0.java
2. 运行程序并观察崩溃
java ScoreAverage0
预期的错误输出:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.Integer.parseInt(Integer.java:615)
at java.lang.Integer.parseInt(Integer.java:676)
at ScoreAverage0.main(ScoreAverage0.java:14)
问题分析:
- ❌ 程序因为数据格式错误而直接崩溃
- ❌ 没有任何用户友好的错误提示
- ❌ 异常信息对普通用户来说很难理解
第二步:学习异常处理(ScoreAverage1.java)
1. 编译改进版本
javac ScoreAverage1.java
2. 测试场景A - 数字格式错误
java ScoreAverage1
输出(程序不崩溃):
Invalid number format in file: For input string: "abc"
No scores to average.
3. 测试场景B - 正常数据
修改代码中的文件名为 score_valid.txt,然后:
javac ScoreAverage1.java
java ScoreAverage1
输出:
Average score: 88.0
Average score: 88.0
4. 测试场景C - 空文件
修改代码中的文件名为 score_empty.txt,然后运行:
javac ScoreAverage1.java
java ScoreAverage1
输出:
No scores to average.
优势分析:
- ✅ 程序不会崩溃
- ✅ 用户能看到清晰的警告信息
- ✅ 程序能够优雅地处理各种错误情况
核心学习目标
🎯 为什么异常处理很重要?
通过比较两个版本,我们学到:
| 对比项 | ScoreAverage0 ❌ | ScoreAverage1 ✅ |
|---|---|---|
| 程序健壮性 | 易崩溃 | 稳定可靠 |
| 用户体验 | 复杂的技术错误 | 清晰的提示信息 |
| 代码质量 | 不可生产使用 | 生产级可用 |
| 错误来源追踪 | 困难 | 清晰明了 |
| 程序流程 | 异常终止 | 优雅降级 |
ScoreAverage0 - 问题演示
这个版本展示了:
- ❌ 没有 throws Exception 声明
- ❌ 没有 try-catch 块
- ❌ 直接调用可能抛异常的方法
- ❌ 导致程序崩溃的后果
你将发现:
FileReader("file.txt")- 如果文件不存在会崩溃Integer.parseInt(line)- 如果数据不是数字会崩溃br.readLine()- 如果IO错误会崩溃sum / count- 如果count为0会崩溃
ScoreAverage1 - 解决方案演示
这个版本展示了:
- ✅ try-with-resources 自动管理资源
- ✅ 多个 catch 块处理不同异常
- ✅ 友好的出错提示
- ✅ 程序优雅处理错误
关键技巧:
-
try-with-resources:自动关闭 BufferedReader
try (BufferedReader br = new BufferedReader(new FileReader("score.txt"))) { // 代码... } // 自动调用 br.close() -
多 catch 块:按顺序捕获不同异常
catch (NumberFormatException e) { ... } catch (FileNotFoundException e) { ... } catch (IOException e) { ... } -
防御性编程:检查必要的条件
if(count > 0) { double average = (double) sum / count; System.out.println("Average score: " + average); } else { System.out.println("No scores to average."); }
深入理解三种异常
1️⃣ NumberFormatException(数字格式异常)
何时发生:
当 Integer.parseInt() 接收到不能解析的字符串时
Integer.parseInt("abc"); // ❌ 异常!
Integer.parseInt("123"); // ✅ 正常
Integer.parseInt("12.5"); // ❌ 异常!浮点数
在本项目中:
- 文件
score_invalid_format.txt中包含 "abc" - 可以用来触发这个异常
错误消息示例:
java.lang.NumberFormatException: For input string: "abc"
2️⃣ FileNotFoundException(文件不存在异常)
何时发生:
当 FileReader() 无法找到指定的文件时
new FileReader("score.txt"); // ✅ 如果存在
new FileReader("missing.txt"); // ❌ 异常!文件不存在
在本项目中:
- 如果代码中指定的文件不存在
- FileNotFoundException 是 IOException 的子类
错误消息示例:
java.io.FileNotFoundException: score.txt (系统找不到指定的文件。)
3️⃣ IOException(输入输出异常)
何时发生:
readLine()读取失败- 文件权限问题
- 磁盘错误
- 其他 IO 相关问题
在本项目中:
- 捕获所有其他 IO 异常
- 也是 FileNotFoundException 的父类
错误消息示例:
java.io.IOException: 磁盘空间不足
⚠️ ArithmeticException(算术异常)
何时发生:
int result = 10 / 0; // ❌ 异常!除以零
在本项目中:
- 如果 count = 0,计算
sum / count会崩溃 - 这是未经检查的异常(不强制捕获)
- 解决办法:检查条件后再计算
if (count > 0) {
double average = (double) sum / count;
} else {
System.out.println("No scores to average.");
}
运行结果对比
ScoreAverage0 - 不处理异常(会崩溃)
场景1:数据格式错误
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.Integer.parseInt(Integer.java:615)
at java.lang.Integer.parseInt(Integer.java:676)
at ScoreAverage0.main(ScoreAverage0.java:14)
❌ 程序崩溃了!
场景2:文件不存在
Exception in thread "main" java.io.FileNotFoundException: missing.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:150)
at java.io.FileReader.<init>(FileReader.java:72)
at ScoreAverage0.main(ScoreAverage0.java:6)
❌ 程序崩溃了!
ScoreAverage1 - 处理异常(优雅降级)
场景1:数据格式错误 + 正常处理
Invalid number format in file: For input string: "abc"
No scores to average.
✅ 程序继续运行,给出清晰提示
场景2:文件不存在
File not found: missing.txt (系统找不到指定的文件。)
No scores to average.
✅ 程序继续运行,给出清晰提示
场景3:正常数据
Average score: 88.0
Average score: 88.0
✅ 正常计算结果
总结:异常处理的必要性
为什么 ScoreAverage0 需要改进?
| 问题 | 说明 |
|---|---|
| 程序随时可能崩溃 | 任何输入错误都会导致 uncaught exception |
| 用户体验差 | 复杂的堆栈跟踪信息无法理解 |
| 难以维护 | 出错时不知道具体发生了什么 |
| 不信任程序 | 用户无法相信程序的稳定性 |
为什么 ScoreAverage1 更好?
| 优势 | 说明 |
|---|---|
| 程序稳定可靠 | 各种异常都被正确处理 |
| 友好的提示 | 用户能理解发生了什么 |
| 易于维护 | 清晰的日志和错误消息 |
| 值得信任 | 无论输入如何,程序都能应对 |
编程最佳实践
✅ 好的做法:
✓ 捕获可能的异常
✓ 给出有意义的错误提示
✓ 使用 try-with-resources 管理资源
✓ 进行防御性检查(如零数检查)
✓ 记录异常信息便于调试
❌ 不好的做法:
✗ 忽略异常,让程序崩溃
✗ 捕获异常但不处理
✗ 忽悠用户,隐藏真实错误
✗ 不检查输入的有效性
进阶学习主题
-
📚 异常层次结构
- Throwable → Exception & Error
- Exception → Checked & Unchecked
- 了解哪些异常必须捕获
-
🔧 try-finally vs try-with-resources
- 手动资源管理 vs 自动资源管理
- Java 7+ 推荐使用 try-with-resources
-
📝 自定义异常
- 创建特定的异常类
- 在适当的位置抛出自定义异常
-
💾 日志记录
- 使用日志框架(如 Log4j)
- 记录异常堆栈跟踪便于调试
-
🔄 异常链
- 捕获一个异常后抛出另一个异常
- 保留原始异常的信息