09 Java 异常处理

2025-12-04 0 212

Java 异常处理入门:看懂这篇,程序再也不 “崩溃” 了

刚学 Java 的你,是不是也遇到过这种情况:写了一个看似完美的程序,输入 10 和 2 能正常出结果,可一旦输入 10 和 0,程序就突然报错终止,屏幕上一堆看不懂的英文?其实这就是 Java 里的 “异常” 在搞鬼。今天就用通俗的比喻 + 实战代码,带你彻底搞懂异常处理,让你的程序从此更稳健~

一、先搞懂:异常和错误不是一回事

我们可以把程序运行比作一次探险旅程,清晰区分两种 “意外状况”:

1. 异常(Exception):能解决的小麻烦

旅途中难免遇到小意外,但只要提前准备,就能顺利化解,旅程还能继续:

  • 想过河发现桥断了(对应IOException,比如读取文件失败)

  • 打开地图发现是空白(对应NullPointerException,空指针异常)

  • 计算路程时不小心除以零(对应ArithmeticException,算术异常)

    这些情况都是可以预见和处理的,就像绕路、重新找地图一样,处理后程序能正常运行。

2. 错误(Error):挡不住的大灾难

如果遇到毁灭性事件,再怎么准备也没用,只能终止旅程:

  • 突然地震,地面裂开鸿沟(对应OutOfMemoryError,内存溢出)

  • 探险队全员病倒(对应NoClassDefFoundError,类定义未找到)

    这类问题是 JVM 或系统层面的严重故障,程序无法捕获和处理,只能提前预防。

一张表分清异常和错误

特性 异常 (Exception) 错误 (Error)
本质 程序运行中的意外情况 JVM / 系统级严重故障
处理方式 可以用代码捕获处理 无法处理,只能预防
影响范围 不处理会导致当前线程终止 通常导致整个程序崩溃
常见例子 空指针、除以零、文件找不到等 内存溢出、栈溢出等

二、异常与错误的继承关系(类层次结构)

理解它们的继承关系是掌握 Java 异常处理机制的关键。

java.lang.Throwable
├─ java.lang.Error(系统错误,不可处理)
│  ├─ OutOfMemoryError
│  ├─ StackOverflowError
│  ├─ NoClassDefFoundError
│  └─ ...(其他系统级错误)
└─ java.lang.Exception(程序异常,可处理)
   ├─ 检查型异常(必须处理)
   │  ├─ IOException
   │  │  ├─ FileNotFoundException
   │  │  └─ ...
   │  ├─ SQLException
   │  ├─ ClassNotFoundException
   │  └─ ...
   └─ 非检查型异常(无需强制处理,继承自 RuntimeException)
      ├─ RuntimeException
      │  ├─ NullPointerException
      │  ├─ IndexOutOfBoundsException
      │  │  ├─ ArrayIndexOutOfBoundsException
      │  │  └─ StringIndexOutOfBoundsException
      │  ├─ IllegalArgumentException
      │  ├─ ClassCastException
      │  └─ ...
      └─ ...(其他非检查型异常,如 `UnsupportedOperationException`)
  • java.lang.Object:Java 中所有类的根父类。

  • java.lang.Throwable:这是所有错误和异常的顶层父类。只有 Throwable 类型的对象才能被 throw 语句抛出,并且被 catch 语句捕获。它有两个重要的子类:

    • java.lang.Error:用于表示严重的、无法处理的错误。

    • java.lang.Exception:用于表示可以被处理的异常。

      • java.lang.RuntimeExceptionException 的一个重要子类,所有非受检异常都继承自它。

三、异常的两大分类:受检和非受检

Java 里所有异常都继承自java.lang.Exception类,根据处理要求不同,分为两类,重点记清楚区别:

1. 受检异常(Checked Exception)

  • 特点:编译时就会 “提醒” 你必须处理,不处理编译器不让通过
  • 原因:大多是外部环境导致的,比如文件不存在、网络断开
  • 常见例子:IOException(文件读写异常)、SQLException(数据库操作异常)
  • 处理要求:要么用try-catch捕获,要么用throws声明交给调用者处理

2. 非受检异常(Unchecked Exception)

  • 特点:编译时不报错,运行时才可能出现
  • 原因:大多是程序员的逻辑错误导致的,比如数组越界、用了空对象
  • 常见例子:NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组越界)
  • 处理要求:不用强制处理,但要在编码时避免(比如判断数组索引是否合法)

提示:所有非受检异常都继承自RuntimeException类,记住这个核心类就好~

四、异常处理核心:try-catch-finally 三板斧

这是 Java 处理异常的核心机制,就像给风险代码装了 “安全网”,我们用计算器案例一步步拆解:

1. 原始问题代码(会崩溃)

import java.util.Scanner;

public class SimpleCalculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(\"请输入第一个数字: \");
        int num1 = scanner.nextInt();
        System.out.print(\"请输入第二个数字: \");
        int num2 = scanner.nextInt();

        int result = num1 / num2; // 输入0就会崩溃
        System.out.println(\"结果是: \" + result);
        scanner.close();
    }
}

2. try 块:包裹风险代码

把可能出错的代码放进try里,让 Java “尝试” 执行:

try {
    int result = num1 / num2; // 可能抛出异常的代码
    System.out.println(\"结果是: \" + result);
}

3. catch 块:捕获并处理异常

如果try里的代码真的出错,catch就会像安全网一样接住异常,执行备用逻辑:

catch (ArithmeticException e) {
    // 只捕获“算术异常”(比如除以零)
    System.out.println(\"【错误提示】\");
    System.out.println(\"出错原因:\" + e.getMessage()); // 获取具体错误信息
    System.out.println(\"除数不能为零,请重新输入!\");
}

4. finally 块:必执行的收尾工作

不管有没有异常,finally里的代码都会执行,适合做资源清理(比如关文件、关连接):

finally {
    System.out.println(\"n--- 计算结束,资源已释放 ---\");
    scanner.close(); // 确保关闭输入流
}

完整健壮版代码(不崩溃)

import java.util.Scanner;

public class RobustCalculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print(\"请输入第一个数字: \");
        int num1 = scanner.nextInt();
        System.out.print(\"请输入第二个数字: \");
        int num2 = scanner.nextInt();

        try {
            int result = num1 / num2;
            System.out.println(\"结果是: \" + result);
        } catch (ArithmeticException e) {
            System.out.println(\"n【错误提示】\");
            System.out.println(\"出错原因:\" + e.getMessage());
            System.out.println(\"除数不能为零,请重新输入!\");
        } finally {
            System.out.println(\"n--- 计算结束,资源已释放 ---\");
            scanner.close();
        }
    }
}

运行效果:输入 10 和 0 后,程序不会崩溃,而是友好提示错误,完美收尾~

五、进阶技巧:throw 和 throws 关键字

除了被动捕获异常,我们还能主动处理异常,这两个关键字要记牢:

1. throw:手动抛出异常

当检测到非法条件时,主动创建异常并抛出,比如判断除数为 0 时直接报错:

if (num2 == 0) {
    // 手动抛出异常,自定义错误信息
    throw new ArithmeticException(\"除数不能为零\");
}

2. throws:声明异常,转移责任

如果一个方法不想处理异常,可以用throws声明,告诉调用者 “这里可能出错,你要处理”:

// 声明该方法可能抛出算术异常
public static int divide(int num1, int num2) throws ArithmeticException {
    if (num2 == 0) {
        throw new ArithmeticException(\"除数不能为零\");
    }
    return num1 / num2;
}

// 调用者必须处理异常
public static void main(String[] args) {
    try {
        int result = divide(10, 0);
        System.out.println(\"结果是: \" + result);
    } catch (ArithmeticException e) {
        System.out.println(\"捕获到异常: \" + e.getMessage());
    }
}

六、高阶玩法:多异常捕获 + 自定义异常

掌握基础后,解锁两个实用高阶技巧,让异常处理更灵活精准:

1. 多异常捕获:一次处理多种错误

实际开发中,try块可能抛出多种异常(比如同时有算术异常、空指针异常),不用写多个独立catch,有两种高效处理方式:

方式 1:多异常并列捕获(Java 7+ 支持)

多个异常用竖线|分隔,适用于异常处理逻辑相同的场景:

try {
    String str = null;
    int num1 = Integer.parseInt(str); // 可能抛NumberFormatException
    int num2 = 0;
    int result = num1 / num2; // 可能抛ArithmeticException
} catch (NumberFormatException | ArithmeticException e) {
    // 同一逻辑处理两种异常
    System.out.println(\"输入非法或计算错误:\" + e.getMessage());
}
方式 2:分层捕获(父异常在后)

如果不同异常需要不同处理逻辑,按 “子类异常在前、父类异常在后” 的顺序捕获:

try {
    String[] arr = {\"10\", \"2\", null};
    int num1 = Integer.parseInt(arr[0]);
    int num2 = Integer.parseInt(arr[2]); // 可能抛NullPointerException
    int result = num1 / num2; // 可能抛ArithmeticException
} catch (NullPointerException e) {
    // 专门处理空指针异常
    System.out.println(\"数组元素为空,请检查输入:\" + e.getMessage());
} catch (ArithmeticException e) {
    // 专门处理算术异常
    System.out.println(\"计算错误,除数不能为零:\" + e.getMessage());
} catch (Exception e) {
    // 父类异常兜底,捕获其他未预料的异常
    System.out.println(\"未知错误:\" + e.getMessage());
}

️ 注意:不能把父异常(如Exception)放在子类异常前面,否则子类异常的catch块会永远执行不到,编译器会报错。

2. 自定义异常:贴合业务场景的错误提示

Java 内置异常只能覆盖通用场景,实际开发中需要贴合业务的异常(比如 “用户年龄不合法”“余额不足”),这时可以自定义异常类:

自定义异常步骤:
  1. 继承Exception(受检异常)或RuntimeException(非受检异常);
  2. 提供无参构造和带错误信息的构造方法(方便传递异常原因)。
实战:自定义 “年龄不合法异常”
// 自定义受检异常(继承Exception)
public class IllegalAgeException extends Exception {
    // 无参构造
    public IllegalAgeException() {}

    // 带错误信息的构造方法
    public IllegalAgeException(String message) {
        super(message); // 调用父类构造,传递错误信息
    }
}

// 自定义非受检异常(继承RuntimeException)
public class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}
使用自定义异常:
public class UserService {
    // 注册用户:年龄必须在18-60岁之间(受检异常,需throws声明)
    public void register(String name, int age) throws IllegalAgeException {
        if (age < 18 || age > 60) {
            // 手动抛出自定义异常
            throw new IllegalAgeException(\"用户\" + name + \"年龄不合法:\" + age + \"岁,需在18-60岁之间\");
        }
        System.out.println(\"用户\" + name + \"注册成功!\");
    }

    // 转账:余额不足时抛出非受检异常
    public void transfer(String userName, double balance, double amount) {
        if (amount > balance) {
            throw new InsufficientBalanceException(\"用户\" + userName + \"余额不足:当前余额\" + balance + \",转账金额\" + amount);
        }
        System.out.println(\"转账成功!剩余余额:\" + (balance - amount));
    }

    // 测试自定义异常
    public static void main(String[] args) {
        UserService service = new UserService();

        // 处理受检异常(必须try-catch或throws)
        try {
            service.register(\"张三\", 17);
        } catch (IllegalAgeException e) {
            System.out.println(\"注册失败:\" + e.getMessage());
        }

        // 处理非受检异常(可选try-catch)
        try {
            service.transfer(\"李四\", 100, 200);
        } catch (InsufficientBalanceException e) {
            System.out.println(\"转账失败:\" + e.getMessage());
        }
    }
}

运行结果:

注册失败:用户张三年龄不合法:17岁,需在18-60岁之间
转账失败:用户李四余额不足:当前余额100.0,转账金额200.0

六、核心总结:异常处理的 3 个原则

  1. 能处理的异常一定要处理,别让程序粗暴崩溃;
  2. try包裹风险代码,catch精准捕获(多异常按 “子类在前、父类在后” 顺序),finally清理资源;
  3. 受检异常必须处理(try-catchthrows),非受检异常尽量避免(靠逻辑判断),业务异常用自定义异常优化提示。

异常处理是 Java 程序健壮性的基石,刚开始不用追求复杂,先把try-catch-finally用熟,能解决除以零、空指针这些常见问题就够了。随着编程经验增加,再慢慢学习自定义异常、多异常捕获等进阶用法~

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 开发教程 09 Java 异常处理 https://www.zuozi.net/3592.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务