一、异常概念

程序运行时,遇到"意料之外的情况"就会产生异常。比如:

  • 用户输入了非法数据
  • 要读取的文件不存在
  • 网络突然断开

Java把这些意外情况封装成异常对象,程序员可以捕获并处理它们,而不是让程序直接崩溃。

异常的继承体系:

Throwable
├── Error         → JVM级别错误,程序员无需处理(如内存溢出)
└── Exception     → 程序级别异常,需要处理
    ├── RuntimeException      → 运行时异常(非受检)
    └── 其他Exception子类     → 受检异常,编译器强制处理

记住这个核心区别:

  • 受检异常:编译时就必须处理,否则代码无法编译
  • 非受检异常(RuntimeException):运行时才出现,编译器不强制处理

二、异常的抛出与捕捉

抛出异常

当程序遇到异常情况时,JVM会自动抛出一个异常对象。例如:

public class Demo {
    public static void main(String[] args) {
        int[] arr = new int[5];
        arr[10] = 1; // 下标越界,JVM自动抛出 ArrayIndexOutOfBoundsException
    }
}

运行结果:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5

程序遇到异常后立即停止,后续代码不再执行。这就是为什么需要"捕捉"异常。


捕捉异常

使用 try-catch-finally 语法捕捉并处理异常:

try {
    // 可能出现异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 捕捉到异常后的处理逻辑
    System.out.println("捕捉到异常:" + e.getMessage());
} finally {
    // 无论是否发生异常,都会执行
    System.out.println("finally块执行");
}

输出:

捕捉到异常:/ by zero
finally块执行

三个块的职责:

作用
try 包裹可能出异常的代码
catch 捕捉指定类型的异常并处理
finally 收尾工作,如关闭文件、释放资源

捕捉多个异常时,子类异常写在前面

try {
    // ...
} catch (NullPointerException e) {
    System.out.println("空指针:" + e.getMessage());
} catch (Exception e) {
    // Exception是父类,放最后兜底
    System.out.println("其他异常:" + e.getMessage());
}

三、Java常见的异常类

初学阶段最常遇到的异常:

异常类 触发原因 示例
NullPointerException 对null对象调用方法 String s = null; s.length();
ArrayIndexOutOfBoundsException 数组下标越界 arr[10](数组长度为5)
ClassCastException 类型强转失败 (String) new Integer(1)
NumberFormatException 字符串转数字失败 Integer.parseInt("abc")
ArithmeticException 算术错误 10 / 0
IOException 文件/网络IO失败 读取不存在的文件
StackOverflowError 方法无限递归 递归没有终止条件

NullPointerException 是初学者最常遇到的异常,遇到时先检查哪个变量是 null


四、自定义异常

Java内置的异常类不够用时,可以自定义异常,让错误信息更贴近业务。

写法:继承 ExceptionRuntimeException

// 自定义受检异常(继承Exception)
public class AgeException extends Exception {
    public AgeException(String message) {
        super(message);
    }
}

// 使用自定义异常
public class Person {
    private int age;

    public void setAge(int age) throws AgeException {
        if (age < 0 || age > 150) {
            throw new AgeException("年龄不合法:" + age);
        }
        this.age = age;
    }
}

// 调用
public static void main(String[] args) {
    Person p = new Person();
    try {
        p.setAge(-5);
    } catch (AgeException e) {
        System.out.println("捕捉到自定义异常:" + e.getMessage());
    }
}

输出:

捕捉到自定义异常:年龄不合法:-5

继承哪个?

  • 继承 Exception:调用者必须处理(受检)
  • 继承 RuntimeException:调用者可以不处理(非受检,后端项目更常用)

五、在方法中抛出异常

使用 throws 关键字

throws 写在方法签名上,表示"这个方法可能抛出某种异常,调用者来处理":

// 声明方法可能抛出 IOException
public void readFile(String path) throws IOException {
    FileReader fr = new FileReader(path);
}

// 调用者必须处理
public static void main(String[] args) {
    try {
        readFile("test.txt");
    } catch (IOException e) {
        System.out.println("文件读取失败:" + e.getMessage());
    }
}

使用 throw 关键字

throw 写在方法体内,用于主动抛出一个异常对象:

public int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("除数不能为0");
    }
    return a / b;
}

throw vs throws 对比:

throw throws
位置 方法体内 方法签名
作用 抛出一个具体的异常对象 声明方法可能抛出的异常类型
数量 每次只抛一个 可声明多个,用逗号分隔

六、运行时异常

RuntimeException 及其子类统称运行时异常,特点是:

  • 编译器不强制处理
  • 通常由代码逻辑错误引起
  • 可以不写 try-catch,但出现时程序会崩溃
// 这段代码编译通过,但运行时崩溃
public static void main(String[] args) {
    String s = null;
    System.out.println(s.length()); // NullPointerException
}

常见运行时异常及预防方式:

// NullPointerException → 使用前判空
if (s != null) {
    System.out.println(s.length());
}

// ArrayIndexOutOfBoundsException → 访问前检查长度
if (index < arr.length) {
    System.out.println(arr[index]);
}

// NumberFormatException → 转换前校验格式
try {
    int num = Integer.parseInt(input);
} catch (NumberFormatException e) {
    System.out.println("输入不是合法数字");
}

运行时异常虽然不强制处理,但在关键位置(如接收用户输入)仍应捕获,避免程序崩溃。


七、异常的使用原则

1. 不要用异常控制正常流程

// 错误:用异常做条件判断,性能差
try {
    int val = Integer.parseInt(str);
} catch (NumberFormatException e) {
    val = 0;
}

// 正确:先判断
if (str.matches("\\d+")) {
    int val = Integer.parseInt(str);
}

2. 不要吞掉异常(空catch块)

// 错误:出了问题完全不知道
try {
    doSomething();
} catch (Exception e) {
    // 什么都不写
}

// 正确:至少打印异常信息
} catch (Exception e) {
    e.printStackTrace();
}

3. 捕获具体的异常类型,而非一律 Exception

// 不推荐
catch (Exception e) { ... }

// 推荐
catch (IOException e) { ... }
catch (NumberFormatException e) { ... }

4. 异常信息要有意义

// 差
throw new Exception("错误");

// 好
throw new IllegalArgumentException("用户年龄不合法,输入值:" + age);

5. 资源必须关闭(推荐 try-with-resources)

try (FileReader fr = new FileReader("test.txt")) {
    // 使用 fr
} catch (IOException e) {
    e.printStackTrace();
}
// fr 自动关闭,无需 finally

八、实践练习

练习1:捕捉数组越界异常

public class Exercise1 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println(arr[4]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("下标越界:" + e.getMessage());
        }
    }
}

练习2:自定义异常

public class ScoreException extends RuntimeException {
    public ScoreException(String message) {
        super(message);
    }
}

public class Student {
    public void setScore(int score) {
        if (score < 0 || score > 100) {
            throw new ScoreException("分数不合法:" + score);
        }
        System.out.println("设置分数:" + score);
    }

    public static void main(String[] args) {
        Student s = new Student();
        try {
            s.setScore(150);
        } catch (ScoreException e) {
            System.out.println(e.getMessage());
        }
    }
}

练习3:使用 throws 声明异常

public class Exercise3 {
    public static String readFirstLine(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

    public static void main(String[] args) {
        try {
            String line = readFirstLine("hello.txt");
            System.out.println(line);
        } catch (IOException e) {
            System.out.println("文件读取失败:" + e.getMessage());
        }
    }
}

本章总结

知识点 核心要点
异常概念 程序运行时的意外情况,分受检/非受检两类
try-catch-finally 捕捉异常的基本语法,finally必定执行
常见异常类 NPE、越界、类型转换、数字格式等
自定义异常 继承 Exception 或 RuntimeException
throws 方法签名上声明,交给调用者处理
throw 方法体内主动抛出异常对象
运行时异常 不强制处理,但关键位置应捕获
使用原则 不吞异常、不用异常控流、捕获具体类型

更多推荐