Java异常处理入门

Java异常处理入门

1. 引入:为什么需要异常?

1.1 程序总会出错

写代码时,有些情况是我们无法控制的:

java 复制代码
int a = 10 / 0;           // 除数不能为0
int[] arr = new int[5];
arr[10] = 100;            // 数组越界
String s = null;
s.length();               // 空指针

如果没有异常处理,程序会直接崩溃,后面的代码无法执行。

1.2 异常的作用

异常是Java用来处理程序运行时错误的机制。它让程序在出错时能够:

  • 捕获错误,避免崩溃
  • 给出提示,方便调试
  • 执行补救措施
java 复制代码
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除数不能为0");
}
System.out.println("程序继续执行");  // 会执行

2. 异常的体系结构

2.1 继承体系

复制代码
java.lang.Object
    └── java.lang.Throwable
            ├── java.lang.Error(错误,程序无法处理)
            │       ├── OutOfMemoryError
            │       ├── StackOverflowError
            │       └── ...
            │
            └── java.lang.Exception(异常,程序可以处理)
                    ├── RuntimeException(运行时异常,非受检)
                    │       ├── NullPointerException
                    │       ├── ArrayIndexOutOfBoundsException
                    │       ├── ArithmeticException
                    │       ├── ClassCastException
                    │       └── ...
                    │
                    └── 其他Exception(编译时异常,受检)
                            ├── IOException
                            ├── SQLException
                            ├── FileNotFoundException
                            └── ...

2.2 Error vs Exception

类型 说明 是否需要处理
Error JVM内部错误,如内存溢出、栈溢出 程序无法处理
Exception 程序运行中出现的异常 程序应该处理

2.3 受检异常 vs 非受检异常

类型 特点 例子
受检异常 编译时必须处理(try-catch或throws) IOException、SQLException
非受检异常 编译时不强制处理,运行时才出现 NullPointerException、ArrayIndexOutOfBoundsException

  • 受检异常 = 编译器逼你处理的
  • 非受检异常 = 编译器不管,但运行时会崩

3. 异常的处理方式

3.1 try-catch-finally

基本语法

java 复制代码
try {
    // 可能出异常的代码
} catch (异常类型1 变量名) {
    // 处理异常1
} catch (异常类型2 变量名) {
    // 处理异常2
} finally {
    // 无论是否异常,都会执行
}

完整示例

java 复制代码
public class TryCatchDemo {
    public static void main(String[] args) {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]);  // 数组越界
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界:" + e.getMessage());
        } finally {
            System.out.println("finally执行");
        }
        System.out.println("程序继续运行");
    }
}

关于finally:

finally不一定执行(调用System.exit()、JVM崩溃、守护线程强制终止等情况下不执行)。

3.2 多个catch的注意事项

java 复制代码
// 正确:子类异常在前,父类异常在后
try {
    // ...
} catch (NullPointerException e) {
    // 子类
} catch (RuntimeException e) {
    // 父类
}

// 错误:父类在前,子类永远捕获不到
try {
    // ...
} catch (RuntimeException e) {
    // 父类(会拦截所有子类异常)
} catch (NullPointerException e) {
    // 永远不会执行
}

3.3 try-catch-finally的几种组合

组合 说明
try-catch 捕获并处理异常
try-finally 不捕获异常,但保证finally执行
try-catch-finally 完整形式
try-with-resources 自动关闭资源(Java 7+)

3.4 抛出异常:throws

throws:声明方法可能抛出异常,交给调用者处理。

java 复制代码
public class ThrowsDemo {
    // 声明抛出异常,交给调用者处理
    public static void readFile(String path) throws FileNotFoundException {
        FileReader reader = new FileReader(path);
    }
    
    public static void main(String[] args) {
        try {
            readFile("test.txt");
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        }
    }
}

3.5 主动抛出异常:throw

throw:手动抛出异常对象。

java 复制代码
public class ThrowDemo {
    public static void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄不合法:" + age);
        }
        System.out.println("年龄:" + age);
    }
    
    public static void main(String[] args) {
        setAge(200);  // 抛出IllegalArgumentException
    }
}

3.6 throws vs throw

维度 throws throw
位置 方法声明后 方法体内
后面跟的 异常类名 异常对象
作用 声明方法可能抛异常 主动抛出异常对象
数量 可跟多个 只能一个

4. 自定义异常

4.1 为什么要自定义异常?

Java自带的异常虽然多,但业务上可能需要更具体的异常,比如:

  • 年龄不合法异常
  • 余额不足异常
  • 用户名重复异常

4.2 自定义异常的写法

java 复制代码
// 自定义受检异常(继承Exception)
public class AgeIllegalException extends Exception {
    public AgeIllegalException() {
        super();
    }
    
    public AgeIllegalException(String message) {
        super(message);
    }
}

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

4.3 使用自定义异常

java 复制代码
public class UserService {
    public static void setAge(int age) throws AgeIllegalException {
        if (age < 0 || age > 150) {
            throw new AgeIllegalException("年龄必须在0-150之间,当前值:" + age);
        }
        System.out.println("年龄设置成功:" + age);
    }
    
    public static void withdraw(int balance) {
        if (balance < 0) {
            throw new BalanceException("余额不能为负数:" + balance);
        }
    }
    
    public static void main(String[] args) {
        // 受检异常需要处理
        try {
            setAge(200);
        } catch (AgeIllegalException e) {
            System.out.println(e.getMessage());
        }
        
        // 非受检异常不需要强制处理
        withdraw(-100);
    }
}

5. 异常中的常用方法

java 复制代码
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 获取异常信息
    String msg = e.getMessage();           // "/ by zero"
    
    // 打印异常堆栈(最常用)
    e.printStackTrace();                    // 打印完整调用链
    
    // 获取异常类型
    String className = e.getClass().getName();  // "java.lang.ArithmeticException"
    
    // 获取异常原因
    Throwable cause = e.getCause();         // 引发当前异常的原因
}

6. 异常的传播

异常会沿着调用栈向上传递,直到被捕获:

java 复制代码
public class ExceptionPropagation {
    public static void method3() {
        int result = 10 / 0;  // 异常发生在这里
    }
    
    public static void method2() {
        method3();  // 异常向上传播到这里
    }
    
    public static void method1() {
        method2();  // 异常向上传播到这里
    }
    
    public static void main(String[] args) {
        try {
            method1();  // 在这里捕获异常
        } catch (ArithmeticException e) {
            System.out.println("捕获到异常:" + e.getMessage());
        }
    }
}

传播路径

复制代码
method3(发生)→ method2 → method1 → main(捕获)

7. 异常链

一个异常可以引发另一个异常:

java 复制代码
public class ExceptionChain {
    public static void main(String[] args) {
        try {
            try {
                throw new ArithmeticException("除数不能为0");
            } catch (ArithmeticException e) {
                // 包装成另一个异常再抛出
                throw new RuntimeException("计算失败", e);
            }
        } catch (RuntimeException e) {
            System.out.println("异常:" + e.getMessage());
            System.out.println("原始原因:" + e.getCause().getMessage());
        }
    }
}

8. 常见异常及原因

异常 原因
NullPointerException 调用null对象的方法或属性
ArrayIndexOutOfBoundsException 数组索引越界
StringIndexOutOfBoundsException 字符串索引越界
ArithmeticException 算术异常(如除以0)
ClassCastException 类型转换失败
NumberFormatException 字符串转数字失败
IllegalArgumentException 参数不合法
FileNotFoundException 文件不存在
IOException IO操作失败
SQLException 数据库操作失败

9. 易错点总结

  1. catch顺序错误:父类异常写在子类前面,子类永远捕获不到

  2. finally中return:finally中的return会覆盖try/catch中的return

java 复制代码
try {
    return 1;
} finally {
    return 2;  // 实际返回2
}
  1. finally中抛出异常:会覆盖try/catch中的异常

  2. 受检异常不处理:编译时报错,必须try-catch或throws

  3. 空catch块:捕获异常后什么都不做,问题被隐藏

  4. 异常对象用==比较 :应该用instanceofgetClass()


10. 总结对比

对比项 受检异常 非受检异常
继承 Exception(非RuntimeException) RuntimeException
编译时检查
是否强制处理
常见例子 IOException、SQLException NullPointerException、ArrayIndexOutOfBoundsException
对比项 throws throw
位置 方法声明 方法体
后面跟的 异常类名 异常对象
作用 声明可能抛出 主动抛出

相关推荐
极客小云12 小时前
【用 Go 写一个统一的 LLM Token 统计库:tokencalc 的设计与实现】
开发语言·后端·golang
憧憬成为java架构高手的小白12 小时前
苍穹外卖--day07(缓存商品,购物车)
java·spring boot
观无12 小时前
若依框架在window的打包部署
java
Vect__12 小时前
C++转go的之路:变量声明、iota、函数、切片、init、defer
开发语言·后端·golang
晚烛12 小时前
CANN 自定义算子开发:Ascend C 编程接口与算子实现完整指南
c语言·开发语言·人工智能·python
问心无愧051312 小时前
ctf show web入门 254
java·开发语言·笔记
Byte Wizard12 小时前
自定义类型:结构体
c语言·开发语言
郝学胜-神的一滴12 小时前
Qt 高级开发 013: 元对象编译器(MOC)
开发语言·c++·qt·程序人生·用户界面
逸Y 仙X13 小时前
文章三:Elasticsearch 集群恢复和索引分布
java·大数据·linux·服务器·elasticsearch·搜索引擎·全文检索