JAVA-异常处理
1.异常概述
-
异常是运行时错误(程序执行期间发生的事件).
-
异常是从方法抛出的,方法的调用者可以捕获以及处理该异常.
-
异常处理使得程序可以处理运行时的错误并且继续通常的执行.
-
运行时错误: 程序运行过程中,若JVM检测出一个不可能执行的操作.
eg.越界下标访问: ArrayIndexOutOfBoundsException -
异常: JAVA中运行时错误会作为异常抛出. 异常为一种对象.
大致模板:
try{
Code to run;
A statement or a method that may throw an exception;
More code to run;
}
catch(type ex){
Code to process the exception;
}
- 很多库方法都会抛出异常,此时throw可省
- 一个异常可能是通过try块中的throw语句直接抛出,或者调用一个可能会抛出异常的方法而抛出
- 若try块中内容正常执行,不会引起异常
- 若try块中内容(方法)遇到一个异常,其会抛出一个异常给它的调用者,这个调用者的catch处理该异常
拋出(throw)异常:生成异常对象,并把它提交给运行时系统。
捕获(catch)异常:运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象。
2.异常类型
- Java通过面向对象来处理异常,即异常是对象,而对象都用类来定义。异常的根类为java.lang.Throwable
异常类的类型:
- 系统错误Error: Java虚拟机抛出,描述内部系统错误.
发生时:通知用户以及尽量稳妥地终止程序. - 异常Exception:描述由程序和外部环境引起的错误.发生时:能被程序捕获和处理
- 运行时异常(unchecked): RuntimeException类表示,程序设计错误。如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。
- 非运行时异常(checked):RuntimeException以外的异常。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常(一般情况下不自定义检查异常)。
- RuntimeException, Error以及其子类都称为免检异常,其他异常称为必检异常(编译器会强制程序员检查并通过try-catch来处理/方法头中声明)
2.1 Java常见运行时异常:
2.2 Java常见非运行时异常
2.3 Error与Exception比较
- Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。
- Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。不检查异常就是所谓的运行时异常,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。
- Error 是指正常情况下不大可能出现的情况,绝大部分的 Error 都会导致程序处于非正常、不可恢复状态。所以不需要被开发者捕获。
3.异常处理
- 异常的处理器都是通过从当前方法开始,沿着方法调用链,按照异常的反向传播方向找到的.
- Java异常处理模型基于三种操作: 声明异常,抛出异常,捕获异常;五个关键字:try,catch,throw,throws,finally
- try catch语句用于捕获并处理异常
- finally语句在任何情况下(除特殊情况)都是必须执行的代码
- throw语句用于抛出异常
- throws语句用于声明可能会出现的异常
3.1 声明异常
- 当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用 throws 声明的方法表示此方法不处理异常。
具体格式:returnType method_name(paramList) throws Exception 1,Exception2,…{…}
3.1.1 使用思路
使用 throws 声明异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。
JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
3.1.2 注意事项
- Java中当前语句必属于某个方法.每个方法都必须声明其可能抛出的必检异常类型.
- Java不要求在方法中显式声明Error和RuntimeException,其他异常都必须显式声明.
- 关键字throw表明method方法可能会抛出IOException异常;若可能抛出多个异常可用,分隔.
- 若在父类中的方法没有声明异常,则不能在子类中对其重写时声明异常.
3.1.3 使用限制
子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
3.1.4 使用示例
点击查看代码
public class Test04 {
public void readFile() throws IOException {
// 定义方法时声明异常
FileInputStream file = new FileInputStream("read.txt"); // 创建 FileInputStream 实例对象
int f;
while ((f = file.read()) != -1) {
System.out.println((char) f);
f = file.read();
}
file.close();
}
public static void main(String[] args) {
Throws t = new Test04();
try {
t.readFile(); // 调用 readFHe()方法
} catch (IOException e) {
// 捕获异常
System.out.println(e);
}
}
}
以上代码,首先在定义 readFile() 方法时用 throws 关键字声明在该方法中可能产生的异常,然后在 main() 方法中调用 readFile() 方法,并使用 catch 语句捕获产生的异常。
3.2 抛出异常
3.1.1 使用思路
注: 声明异常关键字:throws, 抛出异常关键字:throw
-
throw 语句用来直接拋出一个异常,后接一个可拋出的异常类对象
throw ExceptionObject;
-
当 throw 语句执行时,它后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。
3.3.2 注意事项
- 检测到错误的程序可以创建一个合适的异常类型的实例并抛出它
- ExceptionObject必须是Throwable类或其子类的对象。
- 如例所示,IllegalArgumentException是Java API中的一个异常类.
- 通常,JAVA API中的每个异常类都至少有两个构造方法:一个无参和一个带String参.String参称为异常消息,可通过一个异常对象调用getMessage()获取
- 一般通过if-else来使用
eg.要求方法的参数非负,但传入了一个负参数值:
IllegalArgumentException ex =
new IllegalArgumentException("Wrong Argument");
int i = in.nextInt();
if(i > 0) ...;
else throw ex; //1
//throw new IllegalArgumentException("Wrong Argument"); //2
3.1 & 3.2 throw与throws比较区别
- throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;
throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。 - 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,
而在方法(类)内部通过 throw 声明一个具体的异常信息。 - throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法;
throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
3.3 捕获异常
3.3.1 程序示例
try {
逻辑程序块
} catch(ExceptionType1 e) {
处理代码块1
} catch (ExceptionType2 e) {
处理代码块2
throw(e); // 再抛出这个"异常"
} finally {
释放资源代码块
}
3.3.2 过程详解
- 在try-catch中捕获及处理异常
- 处理异常的代码称为异常处理器
- 查找异常处理器:
若try块某条语句抛出异常,Java会跳过try中剩余语句,然后查找异常处理器.
从当前方法开始,沿着方法调用链,按照异常的反向传播方向找到这个处理器.
从第一个到最后一个逐个检查catch块,判断在catch块中的异常实例是否是该异常对象的类型.
若没发现则推出这个方法并将异常传递给这个方法的调用者,继续同样的过程来找处理器.
若没找到处理器,程序会终止并在控制台上打印错误信息.
找到并处理之后,程序会跳过 try 语句块中剩余的语句,转到 catch 语句块后面的第一条语句开始执行。
- 查找异常处理器:
- 如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序将从 catch 语句块后的第一条语句开始执行。
- 一般情况下,无论是否有异常抛出,都会执行finally语句块中的语句。
- finally一般用于关闭try中打开的物理资源(JVM不会回收物理资源,只会回收堆内存中对象所占用的内存)
- 查找处理器的过程称为捕获异常
3.3.3 catch可选输出信息
处理代码块1或处理代码块2可使用如下几个方法输出相应异常信息:
java.lang.Throwable | 作用 |
---|---|
getMessage():String | 返回描述该异常对象的信息(含参构造函数的str) ,输出错误的性质。 |
toString():String | 返回三个字符串的连接: 1)异常类的全名 2) (一个冒号和一个空白) 3) getMessage()方法 |
printStackTrace():void | 控制台上打印Throwable对象和它的调用栈的跟踪信息 ,指出异常的类型、性质、栈层次及出现在程序中的位置 |
getStackTrace(): StackTraceElement[] | 返回一个栈跟踪元素的数组,表示和该异常对象相关的栈的跟踪信息 |
toString() | 给出异常的类型与性质。 |
3.3.4 使用机制&注意事项
使用机制:
- 在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。
- 对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。
注意事项:
- 各种异常类可以从一个共同的父类中派生.若一个catch块可捕获一个父类的异常对象,则其可以捕获那个父类所有子类的异常对象
- catch块中异常被指定的顺序: 必须为先子类后父类(先具体后笼统),否则子类捕获不到。(能更具体的处理最好进行更具体的处理)
- try 后面的花括号{ }不可以省略,try 块里声明的变量只是代码块内的局部变量,它只在 try 块内有效
- catch 块后的花括号{ }也不可以省略。
- try-catch-finally中try是必须的,catch和finally都是可选的(但至少出现其一)
- 除非在 try 块、catch 块中调用了退出虚拟机的方法System.exit(int status),否则不管在 try 块或者 catch 块中执行怎样的代码,出现怎样的情况,异常处理的 finally 块总会执行。
- 通常情况下不在 finally 代码块中使用 return 或 throw 等导致方法终止的语句,否则将会导致 try 和 catch 代码块中的 return 和 throw 语句失效
3.4 自动资源管理
3.4.1 示例用法
try (声明或初始化资源语句) {
// 可能会生成异常语句
} catch(Throwable e1){
// 处理异常e1
} catch(Throwable e2){
// 处理异常e1
} catch(Throwable eN){
// 处理异常eN
}
此时当 try 代码块结束时,自动释放资源。不再需要显式的调用 close() 方法,该形式也称为“带资源的 try 语句”。
3.4.2 注意事项
- try 语句中声明的资源被隐式声明为 final,资源的作用局限于带资源的 try 语句。
- 可以在一条 try 语句中声明或初始化多个资源,每个资源以;隔开即可。
- 需要关闭的资源必须实现了 AutoCloseable 或 Closeable 接口。
4.何时使用异常
- 当错误需要被方法的调用者来处理时,方法应该抛出一个异常
- 异常处理通常需要更多的时间和资源(初始化异常对象,从调用栈返回,沿着方法调用链来传播异常以找到其异常处理器)
- 异常发生在方法中,若想让该方法的调用者处理异常,应创建一个异常对象并将其抛出.(若能在发生异常的方法中处理异常则不需抛出或使用异常)
- 个别方法中的简单错误最好进行局部处理(if)
- 当必须处理不可预料的错误状况时应使用try-catch块(不要用其处理简单的,可预料的情况)
5.重新抛出异常
- 若异常处理器不能处理一个异常或只是简单地希望其调用者注意到该异常,Java匀速该异常处理器重新抛出异常.
eg.
try{
statement;
}catch(Exception ex){
operation before exits;
throw ex;
}
- 语句throw ex重新抛出异常给调用者,以便调用者的其他处理器获得处理ex的机会.(已经捕获了还希望得到进一步处理)
6.创建自定义异常类
- 自定义异常类需要继承 Exception 类或其子类;自定义运行时异常类需继承 RuntimeException 类或其子类。
语法格式:
<class><自定义异常名(XXXException)><extends><Exception>
自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。
点击查看自定义异常类实例
public class TestException extends Exception{
public TestException(){
super();
}
public TestException(String s){
super(s);
}
//输入年龄,当年龄大于100或小于0时则抛出异常
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int age;
while (true) {
try {
age = in.nextInt();
if (age < 0) throw new TestException("small");
else if (age > 100) throw new TestException("large");
else System.out.println(age);
} catch (TestException t) {
System.out.println(t.getMessage());
}
}
}
}
点击查看运行结果
3
3
33
33
333
large
11
11
-22
small