15.Java异常处理:从束手无策到从容应对

📅 2026/7/13 15:51:57
15.Java异常处理:从束手无策到从容应对
目录写在前面一、什么是异常1.1 异常的概念1.2 异常的体系结构1.3 异常的分类二、异常的处理方式2.1 防御式编程的两种思路2.2 抛出异常throw2.3 声明异常throws2.4 捕获异常try-catch2.5 finally无论是否异常都要执行2.6 异常的处理流程三、自定义异常类3.1 实现自定义异常3.2 注意事项四、课后练习总结写在前面相信每一位Java初学者都曾经历过这样的时刻满怀信心地写完代码点击运行结果控制台突然冒出一片刺眼的红色错误信息。程序崩溃了而你一脸茫然地看着那个陌生的异常类名不知道发生了什么更不知道该如何解决。异常是编程路上绕不开的一道坎。但换个角度看异常其实是程序在说话——它在告诉你我这里出问题了快来帮帮我。今天这篇文章我们就来系统地学习Java的异常处理机制。掌握了它你就能听懂程序在说什么并且知道该怎么回应。一、什么是异常1.1 异常的概念异常是指在程序执行过程中发生的一种特殊情况或错误状态。它打断了程序的正常执行流程需要特别处理。在日常开发中即使程序员绞尽脑汁想把代码写得尽善尽美程序运行过程中还是难免会出现各种问题。有些问题通过代码很难完全控制比如数据格式不对、网络不通畅、内存不足等。在Java中程序执行过程中发生的不正常行为被称为异常。不同类型的异常都有对应的类来描述。比如下面这些你一定见过// 算术异常 System.out.println(10 / 0); // Exception in thread main java.lang.ArithmeticException: / by zero // 数组越界异常 int[] array {1, 2, 3}; System.out.println(array[100]); // Exception in thread main java.lang.ArrayIndexOutOfBoundsException // 空指针异常 String str null; System.out.println(str.length()); // Exception in thread main java.lang.NullPointerException1.2 异常的体系结构Java内部维护了一套完善的异常体系结构对各种异常和错误进行分类管理Throwable顶层父类 ├── Error严重问题程序无法处理 │ ├── StackOverflowError │ └── OutOfMemoryError └── Exception可处理的异常 ├── RuntimeException运行时异常/非受查异常 │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ └── ArithmeticException └── 其他受查异常 ├── IOException ├── SQLException └── FileNotFoundException关键理解Throwable异常体系的顶层类派生出Error和Exception两个子类ErrorJava虚拟机无法解决的严重问题比如JVM内部错误、资源耗尽等。一旦发生程序无力回天。典型代表StackOverflowError栈溢出和OutOfMemoryError内存溢出Exception程序员可以通过代码进行处理使程序继续执行。我们平时所说的异常主要指的就是Exception1.3 异常的分类根据发生时机不同异常分为两类编译时异常受查异常在程序编译期间发生的异常编译器会强制要求处理。比如public class Person implements Cloneable { private String name; Override public Person clone() { return (Person) super.clone(); // 编译报错未处理CloneNotSupportedException } }运行时异常非受查异常在程序执行期间发生的异常编译器不强制处理。比如NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException等。注意编译时出现的语法性错误不能称之为异常。比如把System.out.println写成了system.out.println这是编译错误而不是异常。二、异常的处理方式2.1 防御式编程的两种思路面对程序中不可避免的错误主要有两种处理思路LBYL事前防御型在操作之前就做充分的检查public static int getElement(int[] array, int index) { if (array null) { System.out.println(数组为null); return -1; } if (index 0 || index array.length) { System.out.println(下标越界); return -1; } return array[index]; }这种方式的缺陷是正常流程和错误处理流程的代码混在一起整体显得比较混乱。EAFP事后认错型先操作遇到问题再处理public static int getElement(int[] array, int index) { try { return array[index]; } catch (NullPointerException e) { System.out.println(数组为null); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(下标越界); } return -1; }这种方式将正常流程和错误流程分离开来代码更清晰。Java异常处理的核心思想就是EAFP。2.2 抛出异常throw当程序中出现错误时可以使用throw关键字抛出一个异常对象将错误信息告知调用者public static int getElement(int[] array, int index) { if (null array) { throw new NullPointerException(传递的数组为null); } if (index 0 || index array.length) { throw new ArrayIndexOutOfBoundsException(传递的数组下标越界); } return array[index]; }使用throw的注意事项throw必须写在方法体内部抛出的对象必须是Exception或其子类对象如果抛出的是RuntimeException或其子类可以不处理直接交给JVM如果抛出的是编译时异常必须处理否则无法通过编译异常一旦抛出其后的代码就不会执行2.3 声明异常throwsthrows关键字用于在方法声明中列出该方法可能抛出的异常将异常的处理责任转移给调用者public void readConfig(String fileName) throws FileNotFoundException { if (!fileName.equals(config.ini)) { throw new FileNotFoundException(配置文件不是config.ini); } // 读取文件的逻辑... }使用throws的注意事项throws必须跟在方法的参数列表之后声明的异常必须是Exception或其子类如果方法内部可能抛出多个异常throws后跟多个异常类型用逗号隔开如果多个异常类型有父子关系直接声明父类即可调用声明抛出异常的方法时如果该异常是编译时异常调用者必须处理或继续抛出2.4 捕获异常try-catchthrows并没有真正处理异常只是将异常上报给调用者。如果要真正处理异常需要使用try-catchpublic static void main(String[] args) { try { readConfig(other.ini); } catch (FileNotFoundException e) { // 异常的处理方式 System.out.println(e.getMessage()); // 只打印异常信息 System.out.println(e); // 打印异常类型:异常信息 e.printStackTrace(); // 打印信息最全面推荐 } // 异常被处理后这里的代码会继续执行 System.out.println(异常如果被处理了这里的代码也可以执行); }关于异常的处理方式异常种类繁多要根据不同的业务场景来决定对于比较严重的问题比如和金钱相关的场景应该让程序直接崩溃防止造成更严重后果对于不太严重的问题大多数场景可以记录错误日志并通过监控报警程序及时通知程序员对于可能会恢复的问题比如网络相关的场景可以尝试重试使用try-catch的注意事项try块内抛出异常位置之后的代码不会被执行如果抛出异常类型与catch时异常类型不匹配异常不会被成功捕获继续往外抛try中可能抛出多个不同的异常需要用多个catch来捕获如果多个异常的处理方式完全相同可以写成catch(异常类型1 | 异常类型2 e)如果异常之间具有父子关系子类异常必须在父类异常之前捕获try { System.out.println(before); System.out.println(array[100]); System.out.println(after); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(数组下标越界异常); e.printStackTrace(); } catch (NullPointerException e) { System.out.println(空指针异常); e.printStackTrace(); }2.5 finally无论是否异常都要执行有些代码不论程序是否发生异常都需要执行。比如程序中打开的资源——网络连接、数据库连接、IO流等在程序正常或异常退出时必须进行回收。finally就是用来解决这个问题的try { // 可能会发生异常的代码 } catch (异常类型 e) { // 对捕获到的异常进行处理 } finally { // 此处的语句无论是否发生异常都会被执行到 }为什么需要finally考虑这样一个场景方法中打开了输入流在方法返回之前必须关闭它。如果不用finally可能会出现资源泄漏public int getData() { Scanner scanner new Scanner(System.in); try { int num scanner.nextInt(); return num; } catch (InputMismatchException e) { e.printStackTrace(); } finally { scanner.close(); // 无论是否发生异常都会执行 System.out.println(finally中代码); } return -1; }重要特性finally中的代码一定会在方法返回之前执行。但如果finally中也有return语句它会覆盖try或catch中的return。因此不建议在finally中写return语句。2.6 异常的处理流程当代码中出现异常时会沿着调用栈向上传递程序先执行try中的代码如果try中的代码出现异常结束try中的代码查看与catch中的异常类型是否匹配如果找到匹配的异常类型执行catch中的代码如果没有找到匹配的异常类型将异常向上传递到上层调用者无论是否找到匹配的异常类型finally中的代码都会被执行如果上层调用者也没有处理异常继续向上传递一直到main方法也没有处理交给JVM处理程序异常终止三、自定义异常类Java内置的异常类虽然丰富但不能完全覆盖实际开发中的所有场景。比如实现一个用户登录功能可能需要区分用户名错误和密码错误两种异常。3.1 实现自定义异常自定义异常通常继承自Exception或RuntimeException// 自定义异常用户名错误 class UserNameException extends RuntimeException { public UserNameException(String message) { super(message); } } // 自定义异常密码错误 class PasswordException extends RuntimeException { public PasswordException(String message) { super(message); } }然后在业务代码中使用public class LoginService { private static final String USER_NAME admin; private static final String PASSWORD 123456; public static void login(String userName, String password) { if (!USER_NAME.equals(userName)) { throw new UserNameException(用户名错误); } if (!PASSWORD.equals(password)) { throw new PasswordException(密码错误); } System.out.println(登录成功); } public static void main(String[] args) { try { login(admin, 654321); } catch (UserNameException e) { e.printStackTrace(); } catch (PasswordException e) { e.printStackTrace(); } } }3.2 注意事项继承自Exception的异常默认是受查异常编译时异常必须处理继承自RuntimeException的异常默认是非受查异常运行时异常可以不处理四、课后练习题目1​ 以下哪个不是Java中的受检异常checked exceptionA. IOExceptionB. SQLExceptionC. NullPointerExceptionD. ClassNotFoundException答案​ C题目2​ 关于try-catch-finally语句以下哪个说法是正确的A. finally块总是会执行即使try块中有return语句B. 如果catch块中抛出异常finally块就不会执行C. 一个try块后面必须跟着至少一个catch块D. finally块中的return语句会覆盖try或catch块中的return语句答案​ A、D题目3​ 关于异常处理以下哪个说法是错误的A. 子类方法抛出的异常范围不能大于父类方法声明的异常范围B. 可以在catch块中捕获多个异常类型C. RuntimeException及其子类都是非受查异常D. 使用throw关键字可以手动抛出异常但不能抛出Error答案​ D总结今天我们系统地学习了Java的异常处理机制异常的概念程序执行过程中的不正常行为异常体系Throwable → Error不可处理和 Exception可处理异常分类编译时异常受查异常和运行时异常非受查异常处理方式throw抛出、throws声明、try-catch捕获、finally收尾处理流程沿调用栈向上传递直到被处理或交给JVM自定义异常继承Exception或RuntimeException异常处理不是可有可无的装饰而是高质量软件的必备能力。一个好的程序不是不出错而是在出错时能够优雅地处理给用户友好的反馈给开发者足够的信息来定位问题。掌握异常处理你的代码将更加健壮、更加专业。