异常
异常概述
认识异常
异常是指程序在运行过程中出现的非正常情况,如用户输入错误、除数为零、文件不右在、数组下标越界等。由于异常情况在程序运行过程中是难以避免的,一个良好的应用程除了满足基本功能要求外,还应该具备预见并处理可能发生的各种异常情况。因此,在开发应用程序时需要充分考虑各种意外情况,以提高程序的容错能力。异常处理是一种技术,用于处理这种异常情况。
Java异常体系结构
所有异常类都是Throwable类的子类,它派生出两个子类, Error 类和 Exception 类。
-
(1) Error 类:表示程序无法恢复的严重错误或者恢复起来比较麻烦的错误,例如内存溢出、动态链接失 败、虚拟机错误等。应用程序不应该主动抛出这种类型的错误,通常由虚拟机自动抛出。如果出现这种错误,最 好的处理方式是让程序安全退出。在进行程序设计时,我们更应关注Exception类。
-
(2) Exception 类:由Java应用程序抛出和处理的非严重错误,例如文件未找到、网络连接问题、算术错误 (如除以零)、数组越界、加载不存在的类、对空对象进行操作、类型转换异常等。Exception类的不同子类对应 不同类型的异常。Exception类又可分为两大类异常: 不受检异常:也称为unchecked异常,包括RuntimeException及其所有子类。对这类异常并不要求强制进行处 理,例如算术异常ArithmeticException等。
-
本章将重点讲解不受检异常。 受检异常:也称为checked异常,指除了不受检异常外,其他继承自Exception类的异常。对这类异常要求在代码 中进行显式处理。 Java提供了多种异常类,下表列举了一些常见的异常类及其用途。在当前阶段,你只需要初步了解这些异常类即 可。在以后的编程中,可以根据系统报告的异常信息,分析异常类型来判断程序出现了什么问题。
异常类名 | 异常分类 | 说明 |
---|---|---|
Exceptin | 设计时异常 | 异常层次结构的根类 |
IOException | 设计时异常 | IO异常的根类,属于非运行时异常。 |
FileNotFoundException | 设计时异常 | 文件操作时,找不到文件。属于 非运行时异常。 |
RuntimeException | 运行时异常 | 运行时异常的根类, RuntimeException及其子类,不 要求必须处理。 |
ArithmeticException | 运行时异常 | 算术运算异常,比如:除数为 零,属于运行时异常。 |
IllegalArgumentException | 运行时异常 | 方法接收到非法参数,属于运行 时异常。 |
ArrayIndexOutOfBoundsException | 运行时异常 | 数组越界访问异常,属于运行时 异常。 |
NullPointerException | 运行时异常 | 尝试访问null对象的成员时发生的 空指针异常,属于运行时异常。 |
ArrayStoreException | 运行时异常 | 数据存储异常,写数组操作时, 对象或数据类型不兼容 |
ClassCastException | 运行时异常 | 类型转换异常 |
IllegalThreadStateException | 运行时异常 | 试图非法改变线程状态,例如试 图启动一已经运行的线程 |
NumberFormatException | 运行时异常 | 数据格式异常,试图把一字符串 非法转换成数值 |
Java异常处理机制
异常处理
Java的异常处理机制类似于人们对可能发生的意外情况进行预先处理的方式。在程序执行过程中,如果发生了异 常,程序会按照预定的处理方式对异常进行处理。处理完异常之后,程序会继续执行。如果异常没有被处理,程 序将终止运行。
-
Java的异常处理机制依靠以下5个关键字:try、catch、finally、throw、throws。这些关键字提供了两种异常处 理方式。
-
⽤try、catch、finally来捕获和处理异常。
-
try块中包含可能会抛出异常的代码。
-
catch块⽤于捕获并处理指定类型的异常。
-
finally块中的代码⽆论是否发⽣异常都会被执⾏,通常⽤于释放资源或清理操作。
-
使⽤throw、throws来抛出异常。
-
throw关键字⽤于⼿动抛出异常对象。
-
throws关键字⽤于在⽅法声明中指定可能抛出的异常类型,表⽰该⽅法可能会抛出该类型的异常,由调⽤ 者来处理。
捕获异常
使用try-catch处理异常
Java中提供了try-catch结构进行异常捕获和处理,把可能出现异常的代码放在try语句块中,并使用catch语句块 捕获异常。 使用try-catch捕获并处理异常。
try-catch语句块首先执行try语句块中的语句,这时可能会出现以下3种情况:
1.如果try语句块中的所有语句正常执⾏完毕,没有发⽣异常,那么catch语句块中的所有语句都将被忽略。
2.如果try语句块在执⾏过程中发⽣异常,并且这个异常与catch语句块中声明的异常类型匹配,那么try语句块 中剩下的代码都将被忽略,⽽相应的catch语句块将会被执⾏。匹配是指catch所处理的异常类型与try块所⽣ 成的异常类型完全⼀致或是它的⽗类。
3.如果try语句块在执⾏过程中发⽣异常,⽽抛出的异常在catch语句块中没有被声明,那么程序⽴即终⽌运⾏, 程序被强迫退出。
4.catch语句块中可以加⼊⽤⼾⾃定义处理信息,也可以调⽤异常对象的⽅法输出异常信息,常⽤的⽅法如下:
void prinStackTrace() :输出异常的堆栈信息。堆栈信息包括程序运行到当前类的执行流程,它将输出从方法 调用处到异常抛出处的方法调用的栈序列。
String getMessage() :返回异常信息描述字符串,该字符串描述了异常产生的原因,是 printStackTrace() 输 出信息的一部分。
使用try-catch-finally处理异常
try-catch-finally语句块组合使用时,无论try块中是否发生异常,finally语句块中的代码总能被执行。 使用try-catch-finally捕获并处理异常。
public class Main { public static void main(String[] args) { try { int i = 1, j = 0, res; System.out.println("begin"); res = i / j; System.out.println("end"); } catch (ArithmeticException e) { System.out.println("caught"); e.printStackTrace(); } finally { System.out.println("finally"); } System.out.println("over"); } }
输出结果输出了“finally”。如果将j的值改为不是0,再次运行该程序,也会输出“finally”。 try-catch-finally语句块执行流程大致分为如下两种情况。
1.如果try语句块中所有语句正常执⾏完毕,程序不会进⼊catch语句块执⾏,但是finally语句块会被执⾏。
2.如果try语句块在执⾏过程中发⽣异常,程序会进⼊到catch语句块捕获异常, finally语句块也会被执⾏。
3.try-catch-finally结构中try语句块是必须存在的,catch、finally语句块为可选,但两者⾄少出现其中之⼀。
-
即使在catch语句块中存在return语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执 行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语 句退出。
-
finally语句块中语句不执行的唯一情况是在异常处理代码中执行了 System.exit(1) ,退出Java虚拟机。
使用多重catch处理异常
当一段代码可能引发多种类型的异常时,可以在一个try语句块后面跟随多个catch语句块,分别处理不同类型的 异常。
-
catch语句块的排列顺序必须是从子类到父类,最后一个一般是Exception类。这是因为在异常处理中, catch语句块会按照从上到下的顺序进行匹配,系统会检测每个catch语句块处理的异常类型,并执行第一 个与异常类型匹配的catch语句块。如果将父类异常放在前面,子类异常的catch语句块将永远不会被执 行,因为父类异常的catch语句块已经处理了异常。
一旦系统执行了与异常类型匹配的catch语句块,并执行其中的一条catch语句后,其后的catch语句块将被忽略, 程序将继续执行紧随catch语句块的代码。 总结起来,异常处理的顺序和匹配原则非常重要。子类异常应该放在前面,父类异常应该放在后面,以确保正确 的异常处理顺序。
public class Main { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.println("计算开始"); int i, j, res; System.out.println("请输入被除数"); i = input.nextInt(); System.out.println("请输入除数"); j = input.nextInt(); res = i / j; System.out.println(i + "/" + j + "=" + res); System.out.println("计算结束"); } catch (InputMismatchException e) { System.out.println("除数和被除数必须都是整数"); } catch (ArithmeticException e) { System.out.println("除数不能为零"); } catch (Exception e) { System.out.println("其他异常" + e.getMessage()); } finally { System.out.println("感谢使用本程序"); } System.out.println("程序结束"); } }
抛出异常
使用throws声明抛出异常
try-catch-finally处理的是在一个方法内部发生的异常,在方法内部直接捕获并处理。如果在一个方法体内抛出了 异常,并希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常,以 通知调用者。throws可以同时声明多个异常,之间用逗号隔开。
public void test() throws Exception { }
使用throw抛出异常
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现并解决的,如年龄不在正常范围之内, 性别输入的不是“男”或“女”等,此时需要程序员而不是系统来自行抛出异常,把问题提交给调用者去解决。在 Java语言中,可以使用throw关键字来自行抛出异常。
throw new Exception("message")
-
如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句要么处于 try 块⾥,显式捕获该异常,要么放 在⼀个带 throws 声明抛出的⽅法中,即把该异常交给该⽅法的调⽤者处理;
-
如果 throw 语句抛出的异常是 Runtime 异常,则该语句⽆须放在 try 块⾥,也⽆须放在带 throws 声明抛出 的⽅法中;程序既可以显式使⽤ try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给 该⽅法调⽤者处理自行抛出Runtime 异常比自行抛出Checked 异常的灵活性更好。
-
同样,抛出 Checked 异常则可以让编译器提醒 程序员必须处理该异常。
1.作⽤不同:throw⽤于程序员⾃⾏产⽣并抛出异常,throws⽤于声明该⽅法内抛出了异常。
2.使⽤位置不同:throw位于⽅法体内部,可以作为单独的语句使⽤;throws必须跟在⽅法参数列表的后 ⾯,不能单独使⽤。
3.内容不同:throw抛出⼀个异常对象,只能是⼀个;throws后⾯跟异常类,可以跟多个。
⾃定义异常
经过刚才的学习已经认识了什么是异常了,但是无法为编码过程全部问题都提供异常类,如果企业自己的某种问 题,想通过异常来表示,那就需要自己来定义异常类了。 我们通过一个实际场景,来给大家演示自定义异常。
需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0或者>=150就认为年龄不 合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。 分析:Java的API中是没有年龄非常这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异 常,然后再方法中抛出自定义异常即可
-
先写⼀个异常类AgeIllegalException(这是⾃⼰取的名字,名字取得很奈斯),继承
// 1、必须让这个类继承自Exception,才能成为一个编译时异常类。 public class AgeIllegalException extends Exception{ public AgeIllegalException() { } public AgeIllegalException(String message) { super(message); } }
-
再写⼀个测试类,在测试类中定义⼀个saveAge(int age)⽅法,对age判断如果年龄不在0~150之间,就抛出 ⼀个AgeIllegalException异常对象给调⽤者。
public class ExceptionTest2 { public static void main(String[] args) { // 需求:保存一个合法的年 try { saveAge2(225); System.out.println("saveAge2底层执行是成功的!"); } catch (AgeIllegalException e) { e.printStackTrace(); System.out.println("saveAge2底层执行是出现bug的!"); } } //2、在方法中对age进行判断,不合法则抛出AgeIllegalException public static void saveAge(int age){ if(age > 0 && age < 150){ System.out.println("年龄被成功保存: " + age); }else { // 用一个异常对象封装这个问题 // throw 抛出去这个异常对象 throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age); } } }
-
注意,⾃定义异常可能是编译时异常,也可以是运⾏时异常
1.如果自定义异常类继承Excpetion,则是编译时异常。
特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。
2.如果自定义异常类继承RuntimeException,则运行时异常。
特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。
异常链
有时候我们会捕获一个异常后再抛出另一个异常顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。
定义testOne,testTwo,testThree方法,testTwo对testOne抛出的异常进行捕获,testThree对testTwo抛出的 异常进行捕获:
public class TryDemoFive { public static void main(String[] args) { try { testThree(); } catch (Exception e) { e.printStackTrace(); // } } public static void testOne() throws MyException { throw new MyException("我是一个异常"); } public static void testTwo() throws Exception { try { testOne(); } catch (Exception e) { throw new Exception("我是新产生的异常1"); } } public static void testThree() throws Exception { try { testTwo(); } catch (Exception e) { throw new Exception("我是新产生的异常2"); } } }
-
直接在新抛出的异常中添加原来的异常信息
throw new Exception("我是新产生的异常1", e);
-
在要抛出的对象中使⽤ initCause() ⽅法,添加上⼀个产⽣异常的信息; getCause() 可以获取当前异常对象 的上⼀个异常对象
在要抛出的对象中使⽤ initCause() ⽅法,添加上⼀个产⽣异常的信息; getCause() 可以获取当前异常对象 的上⼀个异常对象
方法结束运行的三种情况
-
方法中的代码执行完毕时,方法结束运行
-
方法中执行return语句后,方法结束运行
-
方法中抛出异常后,方法结束运行