diff --git a/w7/README.md b/w7/README.md new file mode 100644 index 0000000..569b971 --- /dev/null +++ b/w7/README.md @@ -0,0 +1,374 @@ +# 成绩平均分计算项目 - 异常处理学习 (Score Average Calculator - Exception Handling) + +## 项目概述 + +这是一个Java学习项目,**重点强调异常处理的必要性**。通过对比两个版本的代码展示: +- ❌ 没有异常处理的代码会如何崩溃 +- ✅ 使用异常处理的代码如何优雅应对错误 + +## 项目结构 + +### Java源代码文件 + +#### `ScoreAverage0.java` ⚠️ +- **描述**:未进行异常处理的版本(用于演示问题) +- **特点**: + - **移除了 `throws Exception`** - 强制必须处理异常 + - 没有 try-catch 块 + - 直接调用会抛异常的方法: + - `FileReader()` - 可能抛 FileNotFoundException + - `BufferedReader.readLine()` - 可能抛 IOException + - `Integer.parseInt()` - 可能抛 NumberFormatException + - **会导致程序崩溃** ❌ +- **适用场景**: + - 理解"如果不处理异常会怎样" + - 强调异常处理的重要性 + - 作为负反例学习 + +#### `ScoreAverage1.java` ✅ +- **描述**:改进版本,具备完整的异常处理 +- **特点**: + - 使用 try-catch 块捕获三种异常: + - `NumberFormatException`:处理数字格式错误 + - `FileNotFoundException`:处理文件不存在 + - `IOException`:处理其他IO错误 + - 使用 try-with-resources 自动关闭资源 + - 检查成绩数量,防止零数除 + - **程序不会崩溃** ✅ +- **适用场景**:生产级代码,具备健壮的错误处理 + +## 测试数据文件与异常演示 + +### 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. 编译代码 +```bash +javac ScoreAverage0.java +``` + +#### 2. 运行程序并观察崩溃 +```bash +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. 编译改进版本 +```bash +javac ScoreAverage1.java +``` + +#### 2. 测试场景A - 数字格式错误 +```bash +java ScoreAverage1 +``` + +**输出(程序不崩溃):** +``` +Invalid number format in file: For input string: "abc" +No scores to average. +``` + +#### 3. 测试场景B - 正常数据 +修改代码中的文件名为 `score_valid.txt`,然后: +```bash +javac ScoreAverage1.java +java ScoreAverage1 +``` + +**输出:** +``` +Average score: 88.0 +Average score: 88.0 +``` + +#### 4. 测试场景C - 空文件 +修改代码中的文件名为 `score_empty.txt`,然后运行: +```bash +javac ScoreAverage1.java +java ScoreAverage1 +``` + +**输出:** +``` +No scores to average. +``` + +**优势分析:** +- ✅ 程序不会崩溃 +- ✅ 用户能看到清晰的警告信息 +- ✅ 程序能够优雅地处理各种错误情况 + +## 核心学习目标 + +### 🎯 为什么异常处理很重要? + +通过比较两个版本,我们学到: + +| 对比项 | ScoreAverage0 ❌ | ScoreAverage1 ✅ | +|-------|-----------------|-----------------| +| 程序健壮性 | 易崩溃 | 稳定可靠 | +| 用户体验 | 复杂的技术错误 | 清晰的提示信息 | +| 代码质量 | 不可生产使用 | 生产级可用 | +| 错误来源追踪 | 困难 | 清晰明了 | +| 程序流程 | 异常终止 | 优雅降级 | + +### ScoreAverage0 - 问题演示 + +**这个版本展示了:** +- ❌ 没有 throws Exception 声明 +- ❌ 没有 try-catch 块 +- ❌ 直接调用可能抛异常的方法 +- ❌ 导致程序崩溃的后果 + +**你将发现:** +1. `FileReader("file.txt")` - 如果文件不存在会崩溃 +2. `Integer.parseInt(line)` - 如果数据不是数字会崩溃 +3. `br.readLine()` - 如果IO错误会崩溃 +4. `sum / count` - 如果count为0会崩溃 + +### ScoreAverage1 - 解决方案演示 + +**这个版本展示了:** +- ✅ try-with-resources 自动管理资源 +- ✅ 多个 catch 块处理不同异常 +- ✅ 友好的出错提示 +- ✅ 程序优雅处理错误 + +**关键技巧:** +1. **try-with-resources**:自动关闭 BufferedReader + ```java + try (BufferedReader br = new BufferedReader(new FileReader("score.txt"))) { + // 代码... + } // 自动调用 br.close() + ``` + +2. **多 catch 块**:按顺序捕获不同异常 + ```java + catch (NumberFormatException e) { ... } + catch (FileNotFoundException e) { ... } + catch (IOException e) { ... } + ``` + +3. **防御性编程**:检查必要的条件 + ```java + 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()` 接收到不能解析的字符串时 +```java +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()` 无法找到指定的文件时 +```java +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(算术异常) + +**何时发生:** +```java +int result = 10 / 0; // ❌ 异常!除以零 +``` + +**在本项目中:** +- 如果 count = 0,计算 `sum / count` 会崩溃 +- 这是未经检查的异常(不强制捕获) +- **解决办法**:检查条件后再计算 +```java +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.(FileInputStream.java:150) + at java.io.FileReader.(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 更好? + +| 优势 | 说明 | +|------|------| +| **程序稳定可靠** | 各种异常都被正确处理 | +| **友好的提示** | 用户能理解发生了什么 | +| **易于维护** | 清晰的日志和错误消息 | +| **值得信任** | 无论输入如何,程序都能应对 | + +## 编程最佳实践 + +```java +✅ 好的做法: +✓ 捕获可能的异常 +✓ 给出有意义的错误提示 +✓ 使用 try-with-resources 管理资源 +✓ 进行防御性检查(如零数检查) +✓ 记录异常信息便于调试 + +❌ 不好的做法: +✗ 忽略异常,让程序崩溃 +✗ 捕获异常但不处理 +✗ 忽悠用户,隐藏真实错误 +✗ 不检查输入的有效性 +``` + +## 进阶学习主题 + +1. 📚 **异常层次结构** + - Throwable → Exception & Error + - Exception → Checked & Unchecked + - 了解哪些异常必须捕获 + +2. 🔧 **try-finally vs try-with-resources** + - 手动资源管理 vs 自动资源管理 + - Java 7+ 推荐使用 try-with-resources + +3. 📝 **自定义异常** + - 创建特定的异常类 + - 在适当的位置抛出自定义异常 + +4. 💾 **日志记录** + - 使用日志框架(如 Log4j) + - 记录异常堆栈跟踪便于调试 + +5. 🔄 **异常链** + - 捕获一个异常后抛出另一个异常 + - 保留原始异常的信息 diff --git a/w7/ScoreAverage0.java b/w7/ScoreAverage0.java new file mode 100644 index 0000000..f84d441 --- /dev/null +++ b/w7/ScoreAverage0.java @@ -0,0 +1,19 @@ +import java.io.BufferedReader; +import java.io.FileReader; + +public class ScoreAverage0 { + public static void main(String[] args) { + BufferedReader br = new BufferedReader(new FileReader("score_vali.txt")); + String line; + int sum = 0; + int count = 0; + while ((line = br.readLine()) != null) { + sum += Integer.parseInt(line); + count++; + } + br.close(); + double average = (double) sum / count; + System.out.println("Average score: " + average); + } +} + diff --git a/w7/ScoreAverage0/file_not_found0.png b/w7/ScoreAverage0/file_not_found0.png new file mode 100644 index 0000000..2b9fa99 Binary files /dev/null and b/w7/ScoreAverage0/file_not_found0.png differ diff --git a/w7/ScoreAverage0/score_empty0.png b/w7/ScoreAverage0/score_empty0.png new file mode 100644 index 0000000..2635d99 Binary files /dev/null and b/w7/ScoreAverage0/score_empty0.png differ diff --git a/w7/ScoreAverage0/score_invalid_format0.png b/w7/ScoreAverage0/score_invalid_format0.png new file mode 100644 index 0000000..e4868f6 Binary files /dev/null and b/w7/ScoreAverage0/score_invalid_format0.png differ diff --git a/w7/ScoreAverage0/score_valid.png b/w7/ScoreAverage0/score_valid.png new file mode 100644 index 0000000..bb9b70b Binary files /dev/null and b/w7/ScoreAverage0/score_valid.png differ diff --git a/w7/ScoreAverage1.java b/w7/ScoreAverage1.java new file mode 100644 index 0000000..b2296f3 --- /dev/null +++ b/w7/ScoreAverage1.java @@ -0,0 +1,23 @@ +import java.io.*; + +public class ScoreAverage1 { + public static void main(String[] args) { + String line; + int sum = 0; + int count = 0; + try (BufferedReader br = new BufferedReader(new FileReader("score_empt.txt"))) { + while ((line = br.readLine()) != null) { + sum += Integer.parseInt(line); + count++; + } + double average = (double) sum / count; + System.out.println("Average score: " + average); + } catch (NumberFormatException e) { + System.out.println("Invalid number format in file: " + e.getMessage()); + } catch (FileNotFoundException e) { + System.out.println("File not found: " + e.getMessage()); + } catch (IOException e) { + System.out.println("An error occurred: " + e.getMessage()); + } + } +} diff --git a/w7/ScoreAverage1/file_not_found.png b/w7/ScoreAverage1/file_not_found.png new file mode 100644 index 0000000..c7a9346 Binary files /dev/null and b/w7/ScoreAverage1/file_not_found.png differ diff --git a/w7/ScoreAverage1/score_empty1.png b/w7/ScoreAverage1/score_empty1.png new file mode 100644 index 0000000..8cf2a9c Binary files /dev/null and b/w7/ScoreAverage1/score_empty1.png differ diff --git a/w7/ScoreAverage1/score_invalid_format1.png b/w7/ScoreAverage1/score_invalid_format1.png new file mode 100644 index 0000000..779b7bf Binary files /dev/null and b/w7/ScoreAverage1/score_invalid_format1.png differ diff --git a/w7/ScoreAverage1/score_vaild1.png b/w7/ScoreAverage1/score_vaild1.png new file mode 100644 index 0000000..b0c0e7f Binary files /dev/null and b/w7/ScoreAverage1/score_vaild1.png differ diff --git a/w7/score_empty.txt b/w7/score_empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/w7/score_invalid_format.txt b/w7/score_invalid_format.txt new file mode 100644 index 0000000..b1c5406 --- /dev/null +++ b/w7/score_invalid_format.txt @@ -0,0 +1,5 @@ +85 +92 +abc +88 +95 diff --git a/w7/score_valid.txt b/w7/score_valid.txt new file mode 100644 index 0000000..98ff574 --- /dev/null +++ b/w7/score_valid.txt @@ -0,0 +1,5 @@ +85 +92 +78 +88 +95