💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
## 一、概述 ### 1.1 什么是异常? * 异常本质上是程序上的错误。 * 错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误。 Java 中的异常(Exception)是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,必须使用异常类。 ### 1.2 异常分类 在 Java 中所有异常类型都是内置类 java.lang.Throwable 类的子类。Throwable 类下有两个异常分支 Exception 和 Error。 * Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。 * Error 定义了在通常环境下不希望被程序捕获的异常。Error 类型的异常用于 Java 运行时由系统显示与运行时系统本身有关的错误。它们通常是灾难性的致命错误,不是程序可以控制的。 :-: ![](http://cndpic.dodoke.com/5a3bddc647e617d2f6d34818060a0749) 其中异常类 Exception 又分为运行时异常和非运行时异常,这两种异常有很大的区别,也称为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。 * `运行时异常`都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。 * `非运行时异常`是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。 【选择】下列代码中的异常属于()(选择两项) ``` int a = 0; System.out.println(2 / a); ``` ``` A 非检查型异常 B 检查型异常 C Error D Exception ``` 【选择】()类及其子类所表示的异常是用户程序无法处理的。(选择一项) ``` A NumberFormatException B Exception C Error D RuntimeException ``` ### 1.3 异常处理机制 Java 的异常处理机制提供了一种结构性和控制性的方式来处理程序执行期间发生的事件。异常处理通过 5 个关键字来实现:try、catch、throw、throws 和 finally。异常处理的机制如下: * 在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。 * 对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。 ``` try { 逻辑程序块 } catch (异常类型1 e) { 处理代码块1 } catch (异常类型2 e) { 处理代码块2 throw(e); // 再抛出这个“异常” } finally { 释放资源代码块 } ``` ## 二、使用 try-catch-finally 实现异常处理 在 Java 中通常采用 try-catch-finally 语句来捕获异常并处理。语法格式如下: ``` try { 逻辑代码块1 } catch (异常类型 e) { 处理代码块1 } finally { 释放资源代码 } ``` * 在以上语法中,把可能引发异常的语句封装在 try 语句块中,用以捕获可能发生的异常。 * 如果 try 语句块中发生异常,那么一个相应的异常对象就会被拋出,然后 catch 语句就会依据所拋出异常对象的类型进行捕获,并处理。处理之后,程序会跳过 try 语句块中剩余的语句,转到 catch 语句块后面的第一条语句开始执行。 * 如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序将从 catch 语句块后的第一条语句开始执行。 在以上语法的处理代码块1中,可以使用以下 3 个方法输出相应的异常信息。 * `printStackTrace()`方法:指出异常的类型、性质、栈层次及出现在程序中的位置。 * `getMessage()`方法:输出错误的性质。 * `toString()`方法:给出异常的类型与性质。 【例题】编写一个录入学生姓名、年龄和性别的程序,要求能捕捉年龄不为数字时的异常。 ``` public class Student { private String name; private int age; public Student() {} public Student(String name, int age) { this.setName(name); this.setAge(age); } // getter setter... // toString() } public class Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String name = ""; int age = 0; try { System.out.println("请输入学生姓名:"); name = sc.next(); System.out.println("请输入学生年龄:"); age = sc.nextInt(); } catch (Exception e) { e.printStackTrace(); } System.out.println(new Student(name, age)); } } ``` 在实际开发中,根据 try catch 语句的执行过程,try 语句块和 catch 语句块有可能不被完全执行,而有些处理代码则要求必须执行。例如,程序在 try 块里打开了一些物理资源(如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。 > Java 的垃圾回收机制不会回收任何物理资源,垃圾回收机制只回收堆内存中对象所占用的内存。 使用 try-catch-finally 语句时需注意以下几点: 1. 异常处理语法结构中只有 try 块是必需的,也就是说,如果没有 try 块,则不能有后面的 catch 块和 finally 块; 2. catch 块和 finally 块都是可选的,但 catch 块和 finally 块至少出现其中之一,也可以同时出现; 3. 可以有多个 catch 块,捕获父类异常的 catch 块必须位于捕获子类异常的后面; 4. 不能只有 try 块,既没有 catch 块,也没有 finally 块; 5. 多个 catch 块必须位于 try 块之后,finally 块必须位于所有的 catch 块之后。 6. finally 与 try 语句块匹配的语法格式,此种情况会导致异常丢失,所以不常见。 :-: ![](http://cndpic.dodoke.com/55e91aa6d9f853c9a34c62726567324a) 但是当 try、catch、finally 中加入 return 之后,return 和 finally 的执行顺序让很多人混淆不清。 * `try`中带有`return` ``` public static int show() { try { return 1; } finally { System.out.println("执行finally模块"); } } // 执行 fianlly 模块 // 1 ``` * `try`和`catch`中都带有`return` ``` public static int show() { try { int a = 8 / 0; return 1; } catch (Exception e) { return 2; } finally { System.out.println("执行finally模块"); } } // 执行 finally 模块 // 2 ``` > 当 try 代码块或者 catch 代码块中有 return 时,finally 中的代码总会被执行,且 finally 语句 return 返回之前执行。 * `finally`中带有`return` ``` public static int show() { try { int a = 8 / 0; return 1; } catch (Exception e) { return 2; } finally { System.out.println("执行finally模块"); return 0; } } // 执行 finally 模块 // 0 ``` > 当 finally 有返回值时,会直接返回该值,不会去返回 try 代码块或者 catch 代码块中的返回值。 * `finally`中改变返回值 ``` public static int show() { int result = 0; try { return result; } finally { System.out.println("执行finally模块"); result = 1; } } // 执行finally模块 // 0 ``` > 由输出结果可以看出,在 finally 代码块中改变返回值并不会改变最后返回的内容。 总结为以下几条: * 当 try 代码块和 catch 代码块中有 return 语句时,finally 仍然会被执行。 * 执行 try 代码块或 catch 代码块中的 return 语句之前,都会先执行 finally 语句。 * 无论在 finally 代码块中是否修改返回值,返回值都不会改变,仍然是执行 finally 代码块之前的值。 * finally 代码块中的 return 语句一定会执行。 【选择】数组下标越界,则发生异常,提示为()(选择一项) ``` A IOException B ArithmeticException C SQLException D ArrayIndexOutOfBoundsException ``` 【阅读】运行下列代码,当输入的 num 值为 a 时,系统会输出() ``` public static void main(String[] args) { Scanner input = new Scanner(System.in); try { int num = input.nextInt(); System.out.println("one"); } catch(Exception e) { System.out.println("two"); } finally { System.out.println("three"); } System.out.println("end"); } ``` 【阅读】运行下列代码,输出结果为() ``` public static void main (String[] args) { try { int a = 1 - 1; System.out.println("a = " + a); int b = 4 / a; int c[] = {1}; c[10] = 99; } catch (ArithmeticException e) { System.out.println("除数不允许为零"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组越界"); } } ``` 【选择】下列关于异常的描述,错误的是()(选择两项) ``` A printStackTrace() 用来跟踪异常事件发生时执行堆栈的内容 B catch 块中可以出现同类型异常 C 一个 try 块可以包含多个 catch 块 D 捕获到异常后将输出所有 catch 语句块的内容 ``` 【阅读】假设要输出的 id 值为 a101,name 值为 Tom,程序的执行结果为() ``` public static void main(String[] args) { Scanner input = new Scanner(System.in); try { int id = input.nextInt(); String name = input.next(); System.out.println("id=" + id); System.out.println("name=" + name); } catch (InputMismatchException e) { System.out.println("输入有误"); System.exit(1); e.printStackTrace(); } finally { System.out.println("输入结束"); } } ``` 【阅读】下列代码的运行结果为() ``` public static int test(int b) { try { b += 10; return b; } catch (Exception e) { return 1; } finally { b += 20; return b; } } public static void main (String[] args) { int num = 10; System.out.println(test(num)); } ``` ## 三、使用 throw 和 throws 实现异常处理 Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,然后在方法内部通过 throw 拋出异常对象。 * throws 用来声明一个方法可能抛出的所有异常信息,throw 则是指拋出的一个具体的异常类型。 * 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。 * throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。 ### 3.1 throws 声明异常 当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。可以使用 throws 关键字在方法的头部声明一个异常,其具体格式如下: ``` 返回值类型 方法名(参数列表) throws Exception 1, Exception2, … { … } ``` ``` public static int test(int a, int b) throws ArithmeticException { if (b == 0) { throw new ArithmeticException("算术异常"); } else { return a / b; } } ``` > 注意:在编写类继承代码时要注意,子类在覆盖父类带 throws 子句的方法时,子类的方法声明中的 throws 子句不能出现父类对应方法的 throws 子句中没有的异常类型,因此 throws 子句可以限制子类的行为。也就是说,子类方法拋出的异常不会超过父类定义的范围。 ### 3.2 throw 抛出异常 throw 语句用来直接拋出一个异常,后接一个可拋出的异常类对象,其语法格式如下: ``` throw ExceptionObject; ``` > ExceptionObject 必须是 Throwable 类或其子类的对象。如果是自定义异常类,也必须是 Throwable 的直接或间接子类。 【选择】下列关于这段代码的说法正确的是()(选择两项) ``` public static void test (String str) throws Exception { if (str == null || str.length == 0) { throw new Exception("参数不能为空"); } else { System.out.println("str = " + str); } } public static void main(String[] args) throws Exception { Scanner input = new Scanner(System.in); String str = input.nextLine(); test(str); } ``` ``` A 代码错误,没有对 test() 方法中抛出的异常进行处理 B 若产生异常,将由系统进行异常处理 C 本段代码对 throw 抛出异常对象的处理方法为自己抛出异常自己处理 D 若输入空字符串,代码运行结果为:java.lang.Exception: 参数不能为空 ``` 【选择】在下列代码划线处不可以填入选项中的哪一个异常类型()(选择一项) ``` public static int test(int a, int b) throws ____ { if (b == 0) { throw new ArithmeticException("算术异常"); } else { return a / b; } } ``` ``` A Throwable B Exception C InputMismatchException D ArithmeticException ``` ## 四、自定义异常 如果[](http://c.biancheng.net/java/) Java 提供的内置异常类型不能满足程序设计的需求,这时我们可以自己设计 Java 类库或框架,其中包括异常类型。实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类。 自定义异常的语法形式为: ~~~ class 自定义异常名 extends Exception ~~~ 在编码规范上,一般将自定义异常类的类名命名为 XXXException,其中 XXX 用来代表该异常的作用。 自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。 【例题】编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0~100 岁。 ``` public class MyException extends Exception { public MyException() { super(); } public MyException(String str) { super(str); } } import java.util.InputMismatchException; import java.util.Scanner; public class Test { public static void main(String[] args) { int age; Scanner input = new Scanner(System.in); System.out.println("请输入您的年龄:"); try { age = input.nextInt(); // 获取年龄 if(age < 0) { throw new MyException("您输入的年龄为负数!输入有误!"); } else if(age > 100) { throw new MyException("您输入的年龄大于100!输入有误!"); } else { System.out.println("您的年龄为:"+age); } } catch(InputMismatchException e1) { System.out.println("输入的年龄不是数字!"); } catch(MyException e2) { System.out.println(e2.getMessage()); } } } ```