Java笔记10-异常 Exception
[toc]
# Java笔记10-异常 Exception
异常是在运行程序时产生的一种异常情况。如:文件找不到、网络连接失败、非法参数等。
在 Java 中一个异常的产生,主要有如下三种原因:
- Java 虚拟机内部错误,从而产生的异常。
- 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
- 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。
Java 通过面向对象的方法来处理异常。在一个方法的运行过程中,如果发生了异常,则这个方法会产生一个异常对象,并把它交给当前系统,当前系统会寻找相应的代码来处理这一异常对象。
其中把生成异常对象,并把它交给当前系统的过程称为拋出(throw)异常。当前系统寻找相应的代码来处理这一异常对象,这一个过程称为捕获(catch)异常。
# 异常类型
为了能够及时有效地处理程序中的运行错误,Java 专门引入了异常类。
在 Java 中所有异常类型都是 java.lang.Throwable 类的子类。Throwable 类下有两个异常分支 Exception 和 Error。如下图所示。
Throwable 类
Throwable 类是 Java 语言中所有错误或异常的顶层父类,其他类都继承于该类。Throwable类有两个子类,Error类(错误)和 Exception类(异常),各自都包含大量子类。
Error 类
Error(错误)是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关。例如,Java 虚拟机运行错误。这些错误是不可查的,因为它们在程序代码的控制和处理能力之外。
Exception类
Exception(异常)是程序代码运行时发生的各种不期望发生的事件。可以被Java异常处理机制使用,是程序本身可以捕获并且可以处理的异常。
Error 和 Exception 的区别
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。
Error 表示系统级的错误和程序不必处理的异常,比如内存溢出,不可能指望程序能处理这样的情况,所以不需要被开发者处理。
# 运行时异常
运行时异常指的是,程序代码在编译期间无法发现,在运行的过程中就能发现并处理的异常。一般是由程序逻辑错误引起的,因为编译器无法检查出程序逻辑错误。
RuntimeException 类及其子类都是运行时异常类。例如空指针异常、下标越界异常等。
当程序中可能出现运行时异常,即使没有用 try-catch 语句捕获它,也没有用 throws 子句声明抛出它,程序代码也会编译通过。
# 非运行时异常(编译异常)
非运行时异常指的是,程序代码在编译期间就能发现并处理的异常。一般是由程序语法错误引起的,因为编译器能检查出程序语法错误。
当程序中出现非运行时异常,要么用try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则程序代码的编译不会通过。
RuntimeException 类及其子类以外的异常都是非运行类异常。例如 IOException、SQLException 等以及用户自定义的 Exception 异常等
# 异常处理的机制
在Java程序中,异常处理机制为:抛出异常,捕捉异常。
抛出异常
当一个方法中出现错误引发异常时,方法会创建异常对象并交付给运行时系统,运行时系统负责在整个java系统中寻找处置异常的代码并执行。
这个过程就是抛出异常,即自己不处理异常,而是交给其他代码来处理异常。
捕捉异常
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
当运行时系统找到合适异常处理器时,会调用合适异常处理器的方法,开始捕捉异常并处理。若运行时系统找不到合适的异常处理器,则运行时系统终止。即Java程序终止
# 异常处理的五个关键字
总体来说,由于运行时异常在编译期间无法发现。Java 规定,运行时异常将由 Java 运行时系统自动抛出,并且允许应用程序忽略运行时异常。
对于所有的非运行异常,Java 规定:方法必须手动捕捉,或者声明抛出方法之外。
Java 的手动异常处理通过 5 个关键字来实现:try catch、throw、throws 和 finally。
# try catch语句
在 Java 中通常采用 try catch 语句来捕获异常并处理。语法格式如下
try {
// 可能发生异常的语句
} catch(ExceptionType e) {
// 处理异常语句
e.printStackTrace(); //输出异常的类型、性质、栈层次及出现在程序中的位置
}
2
3
4
5
6
如果 try 语句块中发生异常,那么一个相应的异常对象就会被拋出,然后 catch 语句就会依据所拋出异常对象的类型进行捕获,并处理。处理之后,程序会跳过 try 语句块中剩余的语句,转到 catch 语句块后面的第一条语句开始执行。
如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序将从 catch 语句块后的第一条语句开始执行。
多重catch语句
如果 try 代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在 try 后面跟有多个 catch 代码块。
try {
// 可能会发生异常的语句
} catch(ExceptionType e) {
// 处理异常语句
} catch(ExceptionType e) {
// 处理异常语句
} catch(ExceptionType e) {
// 处理异常语句
...
}
2
3
4
5
6
7
8
9
10
注意:在多个 catch 代码块的情况下,当一个 catch 代码块捕获到一个异常时,其它的 catch 代码块就不再进行匹配。另外,当捕获的多个异常类之间存在父子关系时,捕获异常时一般先捕获子类,再捕获父类。所以子类异常必须在父类异常的前面,否则子类捕获不到。
例子
public class Test03 {
public static void main(String[] args) {
Date date = readDate();
}
public static Date readDate() {
try {
//此处可能发生异常
return date;
} catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# finally语句
finally 语句可以与前面介绍的 try catch 语句块匹配使用,即无论try catch 语句块中是否发生异常,finally 语句块中的代码都会被执行。
finally语句块总是会被执行。它主要用于做一些清理工作(如关闭数据库连接、网络连接和磁盘文件等)。
注意:当finally块执行完成之后,才会回来执行 try 或者 catch 块中的return语句。
注意
- 不管是否出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
- 若finally中包含return语句,则返回值不是try或catch中保存的返回值,而是finally中的return返回值。
语法格式如下所示
// 方式1
try {
// 可能会发生异常的语句
} catch(ExceptionType e) {
// 处理异常语句
} finally {
// 清理代码块
}
// 方式2,很少见
try {
// 逻辑代码块
} finally {
// 清理代码块
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# try catch finally 语句块的执行顺序
- 当 try 没有捕获到异常时:try 语句块中的语句逐一被执行,程序将跳过 catch 语句块,执行 finally 语句块和其后的语句;
- 当 try 中某一条语句出现异常时,程序将跳到 catch 语句块,并与 catch 语句块逐一匹配,找到与之对应的处理程序,其他的 catch 语句块将不会被执行。而 try 语句块中,出现异常之后的语句也不会被执行,catch 语句块执行完后,执行 finally 语句块里的语句,最后执行 finally 语句块后面的语句。
- 如果try语句出现异常,并且没有处理此异常的 catch 语句块时。此异常将会抛给 JVM 处理,finally 语句块里的语句还是会被执行,但 finally 语句块后的语句不会被执行。
- 只有 try 块是必需的,catch 块和 finally 块都是可选的,但 catch 块和 finally 块至少出现其中之一,也可以同时出现;
- finally 块必须位于所有的 catch 块之后。
例子
public class Test {
public static void main(String[] args) {
try {
//程序语句
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("执行 finally 语句");
}
//其他语句
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# try catch finally 中 return 的执行顺序
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int temp = 1;
try {
System.out.println(temp);
return ++temp;
} catch (Exception e) {
System.out.println(temp);
return ++temp;
} finally {
++temp;
System.out.println(temp);
}
}
}
//运行结果:1 3 2
//分析
执行顺序为:
输出try里面的初始temp:1;
++temp之后,temp=2;
保存return里面temp的值:2;
执行finally的语句temp:3,输出temp:3;
返回try中的return语句,返回存在里面的temp的值:2;
输出temp:2。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
知识点:
- 若try代码块内含有return,同时存在finally代码块(代码块内无return)时,先保存return结果,然后执行finally,执行完finall后,最后返回return结果。
- 若finally代码块内含有return时,此时finally代码块内的return值将覆盖掉try代码块中的return值,然后返回覆盖后的return结果。
# throws 声明异常
若某个方法内可能会发生异常。而方法内部又不想处理这些异常。则可以再方法声明上添加 throws 关键字。表示将方法内部的异常抛出到方法的外部,由方法外面的系统程序去进行处理。
throws 语句用在方法定义时声明该方法要抛出的异常类型。
注意:若不通过try catch代码块处理方法内抛出的异常。也可以通过throws继续向上抛出异常。
throws 语法格式如下
public void method_name(paramList) throws Exception 1,Exception2,…{
//....方法内部代码
}
// method_name 表示方法名
// paramList 表示参数列表
// Exception 1,Exception2,… 表示多个异常类
2
3
4
5
6
7
throws 声名异常的规则
- 如果当前方法不知道如何处理这种类型的异常,使用 throws 声明抛出异常的类型。如果抛出的是 Exception 异常类型,则该方法被声明为抛出所有的异常。
- 使用 throws 关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。例如 main 方法也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。
- 如果所有方法都层层上抛获取的异常,最终 JVM 会进行处理,处理也很简单,就是打印异常消息和堆栈信息。并中止程序运行,这就是程序在遇到某些异常后自动结束的原因。
- 如果一个方法内部可能出现非运行时异常,要么用 try-catch 语句捕获,要么用 throws 子句声明将它抛出,否则程序编译不通过。
例子
在下面的例子中,readFile方法声明抛出一个异常,调用者main方法可以手动处理该异常,也可以继续向上抛出异常。
public class Test {
public void readFile() throws IOException {
// 定义方法时声明异常
// 方法内可能产生异常
}
}
public class Test2 {
public static void main(String[] args) {
Test t = new Test();
//手动处理readFile方法抛出的异常
try {
t.readFile(); //调用 readFHe()方法
} catch (IOException e) {
// 捕获异常
System.out.println(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# throw 拋出异常
通过关键字 throw 可以手动抛出一个异常对象。若在方法中使用,则该方法要用 throws 关键字声明该方法会有异常。或者使用try catch代码块捕获处理这个异常。
语法格式如下
throw new MyExceptionObject;
//MyExceptionObject 必须是 Throwable 类或其子类的对象。
2
3
- throw 抛出的只能是 Throwable类 或者其子类的实例对象。
throw 抛出异常的程序逻辑
- 当 throw 语句执行时,它后面的语句将不执行。
- 此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。
- 如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。
例子
public class Test {
public void validateUserName(String username) {
// 判断用户名长度是否大于8位
if (username.length() > 8) {
throw new IllegalArgumentException("用户名长度必须大于 8 位!");
}
}
public static void main(String[] args) {
Test te = new Test();
try {
te.validateUserName(username);
} catch (IllegalArgumentException e) {
System.out.println(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# throws与throw的区别
throws 关键字和 throw 关键字在使用上的几点区别如下
- throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常对象,执行 throw 则一定抛出了某个异常对象。
- 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常类型,而在方法(类)内部通过 throw 抛出一个具体的异常对象。
- throws 通常不用显示地捕获异常,由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
# 常见异常
运行时异常
- java.lang.ArrayIndexOutOfBoundsException 数组索引越界异常。当索引值为负数或大于等于数组大小时抛出。
- java.lang.ArithmeticException 算术条件异常。譬如:整数除零等。
- java.lang.NullPointerException 空指针异常。
- java.lang.ClassNotFoundException 找不到类异常。当程序根据字符串形式的类名构造类,而在遍历之后找不到对应名称的 class 文件时,抛出该异常。
- java.lang.IllegalArgumentException 非法参数异常
非运行时异常
- IOException:操作输入流和输出流时可能出现的异常。
- EOFException 文件已结束异常。
- FileNotFoundException 文件未找到异常。
- ClassCastException 类型转换异常类
- SQLException 操作数据库异常类
- NoSuchFieldException 字段未找到异常
# 自定义异常
使用 Java 内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承 Exception 类即可。
创建自定义异常类
class MyException extends Exception {
public MyException() {
super();
}
public MyException(String s) {
super(s);
}
}
2
3
4
5
6
7
8
自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给父类的构造方法。
注意:因为自定义异常继承自 Exception 类,因此自定义异常类中已经包含了父类所有的属性和方法。
使用自定义异常类的步骤。
- 创建自定义异常类。
- 在方法中通过 throw 关键字抛出异常对象。
- 如果在当前抛出异常的方法中处理异常,可以使用 try-catch 语句捕获并处理;否则在方法的声明处通过 throws 关键字指明要抛出异常给方法调用者。
- 在出现异常方法的调用者中捕获并处理异常。
public class Test07 {
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());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21