【Java全栈教程】第18课:异常处理

第18课:异常处理

本课目标:理解异常处理机制,掌握try-catch-finally、throw/throws的使用,能够创建和处理自定义异常。


一、概念讲解

1.1 什么是异常

异常是程序运行时发生的不正常事件,会中断程序的正常执行流程。Java的异常处理机制让程序能够优雅地处理错误,而不是直接崩溃。

1.2 异常体系

php 复制代码
异常继承体系:

                        Throwable
                       /         \
                    Error       Exception
                   /               \
        OutOfMemoryError     RuntimeException
        StackOverflowError    /        \
                   NullPointerException
                   ArrayIndexOutOfBounds
                   ArithmeticException
                   ...
                             IOException
                             SQLException
                             FileNotFoundException
                             ...

Error:系统级错误,程序无法处理(如内存溢出)
RuntimeException:运行时异常,可处理可不处理
Exception(非Runtime):编译时异常,必须处理

1.3 Checked vs Unchecked异常

类型 特点 示例
Checked Exception 编译时检查,必须处理 IOException, SQLException
Unchecked Exception 运行时才出现,可以不处理 NullPointerException, ArrayIndexOutOfBoundsException
Error 系统错误,程序无法处理 OutOfMemoryError, StackOverflowError

二、语法格式

2.1 try-catch-finally

java 复制代码
try {
    // 可能抛出异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 处理特定异常
    System.out.println("算术错误: " + e.getMessage());
} catch (Exception e) {
    // 处理其他异常
    System.out.println("其他错误: " + e.getMessage());
} finally {
    // 无论如何都会执行(除非JVM退出)
    System.out.println("清理工作");
}

2.2 throw和throws

java 复制代码
// throw:在方法体内抛出异常
public int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("除数不能为零");
    }
    return a / b;
}

// throws:在方法签名上声明可能抛出的异常
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // ...
}

// throws可以声明多个异常
public void process(String path) throws IOException, SQLException {
    // ...
}

2.3 自定义异常

java 复制代码
// 自定义受检异常
public class BusinessException extends Exception {
    private int code;

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

// 自定义运行时异常
public class InvalidDataException extends RuntimeException {
    public InvalidDataException(String message) {
        super(message);
    }
}

2.4 Try-with-resources

java 复制代码
// 自动关闭资源(JDK7+)
try (FileReader reader = new FileReader("file.txt");
     BufferedReader br = new BufferedReader(reader)) {

    String line = br.readLine();
    System.out.println(line);

} catch (IOException e) {
    System.out.println("读取失败: " + e.getMessage());
}
// reader和br会自动关闭,无需finally

三、代码案例

3.1 带异常处理的文件读取器

java 复制代码
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SafeFileReader {

    public static List<String> readLines(String filePath) {
        List<String> lines = new ArrayList<>();

        // 检查文件是否存在
        File file = new File(filePath);
        if (!file.exists()) {
            System.out.println("文件不存在: " + filePath);
            return lines;
        }

        // 使用try-with-resources自动关闭资源
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            int lineNumber = 1;

            while ((line = reader.readLine()) != null) {
                lines.add(line);
                lineNumber++;
            }

            System.out.println("成功读取 " + lines.size() + " 行");

        } catch (FileNotFoundException e) {
            System.out.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("读取文件失败: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("未知错误: " + e.getMessage());
        }

        return lines;
    }

    public static boolean writeLines(String filePath, List<String> lines) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            for (String line : lines) {
                writer.write(line);
                writer.newLine();
            }
            System.out.println("成功写入 " + lines.size() + " 行");
            return true;

        } catch (IOException e) {
            System.out.println("写入文件失败: " + e.getMessage());
            return false;
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 文件读取器 ===");

        // 写入测试数据
        List<String> data = new ArrayList<>();
        data.add("Hello World");
        data.add("Java 异常处理");
        data.add("Try-with-resources");
        writeLines("test.txt", data);

        // 读取文件
        List<String> lines = readLines("test.txt");
        for (String line : lines) {
            System.out.println("  " + line);
        }

        // 读取不存在的文件
        System.out.println();
        readLines("nonexistent.txt");
    }
}

▶ 运行结果:

vbnet 复制代码
=== 文件读取器 ===
成功写入 3 行
成功读取 3 行
  Hello World
  Java 异常处理
  Try-with-resources

文件不存在: nonexistent.txt

3.2 带验证的计算器

java 复制代码
public class Calculator {

    // 自定义异常
    public static class DivisionByZeroException extends Exception {
        public DivisionByZeroException() {
            super("除数不能为零");
        }
    }

    public static class InvalidNumberException extends Exception {
        private String input;

        public InvalidNumberException(String input) {
            super("无效的数字: " + input);
            this.input = input;
        }

        public String getInput() {
            return input;
        }
    }

    public static double parseNumber(String input) throws InvalidNumberException {
        if (input == null || input.trim().isEmpty()) {
            throw new InvalidNumberException(input);
        }

        try {
            return Double.parseDouble(input.trim());
        } catch (NumberFormatException e) {
            throw new InvalidNumberException(input);
        }
    }

    public static double divide(double a, double b) throws DivisionByZeroException {
        if (b == 0) {
            throw new DivisionByZeroException();
        }
        return a / b;
    }

    public static double calculate(String a, String operator, String b) {
        try {
            double num1 = parseNumber(a);
            double num2 = parseNumber(b);

            switch (operator) {
                case "+": return num1 + num2;
                case "-": return num1 - num2;
                case "*": return num1 * num2;
                case "/": return divide(num1, num2);
                default:
                    System.out.println("不支持的运算符: " + operator);
                    return 0;
            }

        } catch (InvalidNumberException e) {
            System.out.println("输入错误: " + e.getMessage());
            return 0;
        } catch (DivisionByZeroException e) {
            System.out.println("计算错误: " + e.getMessage());
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 计算器 ===");

        // 正常计算
        System.out.println("10 + 5 = " + calculate("10", "+", "5"));
        System.out.println("10 - 5 = " + calculate("10", "-", "5"));
        System.out.println("10 * 5 = " + calculate("10", "*", "5"));
        System.out.println("10 / 5 = " + calculate("10", "/", "5"));

        System.out.println();

        // 异常情况
        System.out.println("10 / 0 = " + calculate("10", "/", "0"));
        System.out.println("abc + 5 = " + calculate("abc", "+", "5"));
        System.out.println(" + 5 = " + calculate("", "+", "5"));
    }
}

▶ 运行结果:

ini 复制代码
=== 计算器 ===
10 + 5 = 15.0
10 - 5 = 5.0
10 * 5 = 50.0
10 / 5 = 2.0

10 / 0 = 0.0
输入错误: 无效的数字: abc
输入错误: 无效的数字: 

3.3 多异常处理示例

java 复制代码
import java.util.*;

public class MultiExceptionDemo {

    public static void processArray(int[] array, int index) {
        try {
            System.out.println("元素值: " + array[index]);
            int result = 100 / array[index];
            System.out.println("100 / " + array[index] + " = " + result);

        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界: 索引 " + index + " 不存在");

        } catch (ArithmeticException e) {
            System.out.println("算术错误: " + e.getMessage());

        } catch (Exception e) {
            System.out.println("其他错误: " + e.getClass().getSimpleName());
        }
    }

    // 多重catch(JDK7+)
    public static void multiCatch(String input) {
        try {
            int number = Integer.parseInt(input);
            int result = 100 / number;
            System.out.println("结果: " + result);

        } catch (NumberFormatException | ArithmeticException e) {
            // 一个catch处理多种异常
            System.out.println("错误: " + e.getMessage());

        } catch (Exception e) {
            System.out.println("其他错误: " + e.getMessage());
        }
    }

    // finally与return
    public static int finallyWithReturn() {
        int value = 10;
        try {
            value = 20;
            return value;  // finally会在return之前执行
        } finally {
            value = 30;  // 但这不会影响返回值
            System.out.println("finally中value = " + value);
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 多异常处理 ===");

        // 数组越界
        processArray(new int[]{1, 2, 3}, 5);

        // 算术异常
        processArray(new int[]{1, 0, 3}, 1);

        // 多重catch
        System.out.println();
        multiCatch("abc");
        multiCatch("0");
        multiCatch("5");

        // finally与return
        System.out.println();
        int result = finallyWithReturn();
        System.out.println("返回值: " + result);  // 输出20,不是30
    }
}

▶ 运行结果:

makefile 复制代码
=== 多异常处理 ===
数组越界: 索引 5 不存在
算术错误: / by zero

错误: For input string: "abc"
算术错误: / by zero
结果: 20

finally中value = 30
返回值: 20

四、常见错误

4.1 捕获Exception而不捕获具体异常

java 复制代码
public class CatchAllError {
    public static void main(String[] args) {
        // 错误:捕获所有异常,无法针对性处理
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]);
        } catch (Exception e) {  // 不推荐:太宽泛
            System.out.println("出错了");
        }

        // 正确:捕获具体异常
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界: " + e.getMessage());
        }

        // 注意:catch块的顺序很重要,子类必须在父类前面
        try {
            // ...
        } catch (ArrayIndexOutOfBoundsException e) {
            // 具体异常
        } catch (RuntimeException e) {
            // 父类异常
        } catch (Exception e) {
            // 最顶层异常
        }
    }
}

4.2 空的catch块

java 复制代码
public class EmptyCatchError {
    public static void main(String[] args) {
        // 错误:空catch块吞掉异常,问题被隐藏
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            // 什么都没做!错误被静默忽略
        }

        // 正确:至少记录日志或打印信息
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("捕获异常: " + e.getMessage());
            e.printStackTrace();  // 打印堆栈信息
        }
    }
}

4.3 finally中的return

java 复制代码
public class FinallyReturnError {
    public static int testReturn() {
        try {
            return 1;  // 返回1
        } finally {
            return 2;  // 覆盖了try中的return!返回2
        }
    }

    public static void main(String[] args) {
        // 这是一个危险的模式,应该避免
        int result = testReturn();
        System.out.println("返回值: " + result);  // 输出2,不是1

        // 如果finally中没有return,才会返回try中的值
    }
}

▶ 运行结果:

makefile 复制代码
返回值: 2

4.4 异常处理不当

java 复制代码
import java.io.*;

public class ImproperExceptionHandling {
    public static void main(String[] args) {
        // 错误:在循环中捕获异常后继续执行
        int[] array = {1, 2, 0, 4};
        for (int i = 0; i < array.length; i++) {
            try {
                int result = 100 / array[i];
                System.out.println("100 / " + array[i] + " = " + result);
            } catch (ArithmeticException e) {
                System.out.println("跳过0");
                continue;  // 继续下一个
            }
        }

        // 正确:先验证再操作
        System.out.println();
        for (int i = 0; i < array.length; i++) {
            if (array[i] == 0) {
                System.out.println("跳过0");
                continue;
            }
            int result = 100 / array[i];
            System.out.println("100 / " + array[i] + " = " + result);
        }
    }
}

▶ 运行结果:

ini 复制代码
100 / 1 = 100
100 / 2 = 50
跳过0
100 / 4 = 25

跳过0
100 / 1 = 100
100 / 2 = 50
100 / 4 = 25

五、课后练习

  1. 异常练习:编写一个方法,输入字符串数组,将每个字符串转换为整数,处理NumberFormatException。

  2. 自定义异常:创建一个AgeInvalidException,验证年龄必须在0-150之间。

  3. 文件处理:编写一个文件复制程序,使用try-with-resources处理IOException。

  4. 链式异常:创建一个自定义异常,支持传递原始异常(cause)。

  5. finally分析:分析以下代码的输出,解释原因:

java 复制代码
public static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

六、本课小结

概念 要点
Throwable 所有异常和错误的父类
Error 系统错误,程序无法处理
Exception 程序异常,分为Checked和Unchecked
try-catch-finally 捕获和处理异常
throw 在方法体内抛出异常
throws 在方法签名声明可能抛出的异常
自定义异常 继承Exception或RuntimeException
try-with-resources 自动关闭资源

关键规则:

  • 先捕获具体异常,再捕获通用异常
  • 不要捕获Exception而不做任何处理
  • finally中的return会覆盖try中的return
  • 优先使用try-with-resources自动关闭资源

本课程持续更新中,欢迎关注!

相关推荐
她说..2 小时前
MySQL 与 Java 的 JSON 数据处理
java·mysql·json·springboot
咖啡八杯2 小时前
实体基类设计:BaseEntity 公共字段抽取与 TreeEntity 树形继承
java·架构·代码规范
她的男孩2 小时前
AI 写完 100 万行代码没人 Review,我做了一件事:把 AI 写的代码管起来了
java·人工智能·程序员
Gl�ria2 小时前
Linux 查看日志常用命令
java·linux·servlet
大模型丫丫2 小时前
LangGraph + MCP(Model Context Protocol)完整讲解
java·开发语言·数据库
lemon_sjdk2 小时前
JavaFX 源码深度剖析:揭开 StringBinding 的神秘面纱
java·javafx·源码解析·绑定框架
砚底藏山河3 小时前
拉数管道设计:从一次性脚本到可重启的数据流水线(魔码量化实战 #01)
java·数据库·python·金融·maven
饕餮争锋3 小时前
PATH路径列表介绍
java·linux·服务器
SimonKing3 小时前
一文终结 Java 路径加载争议:斜杠 什么时候该加,什么时候不该加
java·后端·程序员