第七章——Java中的异常处理
一、初识
开发人员都希望编写出的“程序代码”没有错误、运行正常,但在实际的程序开发中,往往存在一些外在因素,如:
操作系统出错;
除数为0;
写程序的时候,不小心写错关键字;
用户输入数据出错;数组下标越界;
需要处理的文件不存在
等情况,这些情况的发生使程序产生运行错误
程序中的错误通常可以分成三大类:
语法错误(Syntax error)—编辑、编译器发现
运行错误(Runtime error)—异常处理机制
逻辑错误(Logic error)—调试(debug)、单元测试(unit test)
今天我们讨论的是运行错误,即Java的异常处理机制
eg1:
package p10_2_Java中的异常;
import java.util.Scanner;
public class DivByZero {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int x,y;
System.out.println("请输入除数x:");
x = input.nextInt();
y = 10/x;
System.out.println("10/x的结果为:"+y);
input.close();
}
}
eg2:
package p10_2_Java中的异常;
import java.util.Scanner;
public class DivByZeroPlus {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int x,y;
System.out.println("请输入除数x:");
if(input.hasNextInt()) {
x = input.nextInt();
if(x == 0) {
System.out.println("除数是0,运算结果为无穷大。");
} else {
y = 10/x;
System.out.println("10/x的结果为:"+y);
}
} else {
System.out.println("您输入的数据不是整数类型。");
}
input.close();
}
}
eg3:
package p10_2_Java中的异常;
import java.util.Scanner;
public class DivByZeroInJava {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int x,y;
System.out.println("请输入除数x:");
try {
x = input.nextInt();
y = 10/x;
System.out.println("10/x的结果为:"+y);
}catch(Exception ex) {
System.out.println("输入的除数数据必须是整数类型,且不能为0。");
}
input.close();
}
}
eg1是一个普通程序,当输入的除数为0或者输入的除数数据不是整数类型时,运行时会报错,程序无法继续执行。
eg2是修改后的代码,由程序员人为参与,把运行时可能出现的错误使用if···else代码块进行了分支处理。但是会使程序的代码量增加,从而使得程序的复杂度也会增加,编程时出错的几率也大大增加。
除了代码量增加外,使用if···else结构代码块来处理上面的问题,还存在如下几个缺点:
1)程序员把大量的精力,放在了异常的判断与处理上,没有更多的时间,来处理实际的业务代码,降低了开发效率;
2)异常代码与逻辑代码融合在一起,降低了程序的可读性,增加了修改和维护的难度。
3)使用if···else穷举的方法,很难把所有的情况都考虑到,程序还是不够健壮。
eg3利用了Java中的异常处理机制
二、Java中的异常
1、定义
Java 中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,必须使用异常类,这可以让程序具有极好的容错性且更加健壮。
2、在 Java 中一个异常的产生,主要有如下三种原因:
(1)Java 内部错误发生异常,Java 虚拟机产生的异常。
(2)编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
(3)通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。
3、Java 通过面向对象的方法来处理异常
在一个方法的运行过程中,如果发生了异常,则这个方法会产生代表该异常的一个对象,并把它交给运行时的系统,运行时系统寻找相应的代码来处理这一异常。
我们把生成异常对象,并把它提交给运行时系统的过程称为拋出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象,这一个过程称为捕获(catch)异常。
基本写法:
try{ 语句组
}catch(Exception ex){
异常处理语句组;
}
三、异常类
为了能够及时有效地处理程序中的运行错误,Java 专门引入了异常类。
在Java语言中,所有的异常都是用类表示的。
当程序发生异常时,会生成某个类的对象。
异常类对象包括:关于异常的信息、类型和错误发生时程序的状态,以及对该错误的详细描述。
Java通过API中“Throwable类的众多子类”,来描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的错误条件。当条件成立时,错误将引发异常。
Java程序用继承的方式来组织各种异常。所有的异常都是Throwable类或其子类,而Throwable类又直接继承于object类。
Throwable类有两个子类:Exception(异常)类和Error(错误)类。
1、Exception(异常)类
可以被捕获并且可能恢复的异常类,用户程序也可以通过继承Exception类,来生成自己的异常类。
(1)运行时异常: 都是RuntimeException类及其子类异常,如NullPointerExceotion、IndexOutOfBoundsException等,这些异常是不检查异常,程序可以选择捕获处理,也可以不处理。
(2)非运行时异常: 是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。
(3)常见的Exception异常类如下:
1) java.awt.AWTException , 抽象窗口工具包AWT出现异常;
2) java.nio.channels.AlreadyBoundException , 尝试绑定已经绑定的、网络套接字时抛出的异常;
3) java.util,zip.DataFormatException , 数据格式错误异常;
4) java.io.FileNotFoundException , 试图打开一个指定路径的文件时,引发的异常。当指定路径的文件不存在,而使用FileInputStream , FileOutputStream或RandomAccessFile构造案例时,就会抛出这个异常;如果这个文件确实存在,但因为某些特殊原因(如:打开一个只读文件进行读写时)也会抛出这个异常。
5) java.net,HttpRetryException , 当流媒体模式被启动,一个HTTP请求需要重新发送,却无法自动重发而引发的异常。
7) java.net.MalformedURLException , 统一资源定位器(URL)的格式不正确,引发的异常。
8) java.net.ProtocolException ,网络协议异常;
9) java.net,SocketException , Socket 操作异常;
10) java.lang.ArithmeticException , 算术(如除数为0)运算时引发的异常;
11)) java.lang.ClassCastException , 类型转换异常;
12) java.lang.IndexOutOfBoundsException , 下标越界异常;
13) java.lang.NullPointerException , 访问一下空对象中的成员时引发的异常;
14) java.lang.NumberFormatException , 数据类型转换错误的异常;
15) java.util.InputMismatchException , 通过Scanner对象输入的数据类型,与接收数据的类型不匹配而引发的异常;
16) java.lang.ArrayIndexOutOfBoundsException , 数组下标越界异常,如定义 int a[ ] = new int[3] ; 引用 int a[3] = {1,2,3} , 引发其中的元素a [3],其中a[3]为第四个元素,因为Java数组引用下标是从0开始,a[0]是第一个元素,类推后a[3]是第四个元素。
2、Error(错误)类
一般情况下,被认为是不可恢复,和不可捕捉的异常类,用户程序不需要处理这类异常。
在捕捉Error类及子类时要多加小心,因为他们通常是出现灾难性错误时被创建,常见的Error异常类如下:
1) java.lang.LinkingeError,一个类A依赖于另一个类B,当类A编译完成后,类B发生了更改,导致A无法找到B,而创建的错误信息对象。
2) java.lang.VirtualMachineError , java虚拟机坏了,或继续运行java虚拟机,所需资源已经耗尽了,而创建的错误信息对象;
3) java.awt . AWTError , 抽象窗口工具包,AWT发生严重错误时而创建的错误信息对象。
三、Java如何进行异常处理
在Java语言中,对于可能出现的异常,都需要预先进行处理,以保证程序的有效运行,否则程序就会出错。
在Java语言中,若某个方法抛出异常,既可以在当前方法中进行捕获、然后处理该异常,也可以将该异常向上抛出,由方法的调用者来处理。
Java中处理异常的流程:
(1)抛出(throw)异常
(2)运行时系统在调用栈中查找——从生成异常的方法开始进行回溯,直到找到
(3)捕获(catch)异常的代码
Java 的异常处理通过 5 个关键字来实现:try、catch、throw、throws 和 finally。
1、关于捕获(try、catch、finally)
(1)捕获异常语法格式:
try{
代码块;
}catch(异常类名 异常形式参数名){
异常处理代码块;
}catch(异常类名 异常形式参数名){
异常处理代码块;
}finally{
无条件执行的代码块;
} //catch语句可以0至多个,可以没有finally语句。
解释:
1)把可能引发异常的语句封装在 try 语句块中,用以捕获可能发生的异常。
2)catch 后的( )里放匹配的异常类,指明 catch 语句可以处理的异常类型,发生异常时产生异常类的实例化对象。
(2)过程:
1)如果 try 语句块中发生异常,那么一个相应的异常对象就会被拋出,然后 catch 语句就会依据所拋出异常对象的类型进行捕获,并处理。处理之后,程序会跳过 try 语句块中剩余的语句,开始执行finally语句块。
2)如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序开始执行finally语句块。
注意:
try…catch 与 if…else 不一样,try、catch 块 后面的花括号{ }不可以省略。另外,try 块里声明的变量只是代码块内的局部变量,它只在 try 块内有效,其它地方不能访问该变量。
(3)在 异常处理代码块 中,可以使用以下 3 个方法输出相应的异常信息:
1)printStackTrace() 方法:指出异常的类型、性质、栈层次及出现在程序中的位置
2)getMessage()方法:输出错误的性质。
3)toString() 方法:给出异常的类型与性质。
(4)多重catch语句
如果 try 代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在 try 后面跟有多个 catch 代码块
在多个 catch 代码块的情况下,当一个 catch 代码块捕获到一个异常时,其它的 catch 代码块就不再进行匹配。
注意:当捕获的多个异常类之间存在父子关系时,捕获异常时一般先捕获子类,再捕获父类。所以子类异常必须在父类异常的前面,否则子类捕获不到
eg:
2、关于抛出(throws、throw)
(1)throws(声明异常)
当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。所以,使用 throws 声明的方法表示此方法不处理异常。
语法格式:
返回类型 方法名 ( 参数列表 ) throws 异常类 , 异常类 ,…{
…
}
注:
- 如果有多个异常类,它们之间用逗号分隔。
- 这些异常类可以是方法中调用了可能拋出异常的方法而产生的异常,也可以是方法体中生成并拋出的异常。
使用 throws 声明抛出异常的思路是,
当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
方法重写时声明抛出异常的限制:
子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
( 2)throw(抛出异常)
语法格式:
throw 异常对象;
其中,异常对象必须是 Throwable 类或其子类的对象。如果是自定义异常类,也必须是 Throwable 的直接或间接子类。
当 throw 语句执行时,它后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。
(3)throws 关键字和 throw 关键字在使用上的几点区别如下:
- throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。
- 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw声明一个具体的异常信息。
- throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
四、自定义异常
根据实际需要,有时需要定义自己的异常类,并将它们用于程序中,来描述Java内置异常类所不能描述的一些特殊情况。
创建用户自定义异常时:
(1)继承自Exception类或某个子Exception类
(2)定义属性和方法,或重载父类的方法。
与创建普通类的语法相同,自定义异常类的使用方法与Java语言内置的异常类的使用方法也相同
eg:
自定义异常
package p10_5;
//自定义异常
public class MyStrChkException extends Exception{
private static final long seriaLVersionUID = 1L;
private String content;
//构造方法
public MyStrChkException(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
测试类
package p10_5;
public class MyStrChkTest {
public static void chkStr(String str) throws MyStrChkException{
char [] array = str.toCharArray();
int len = array.length;
for(int i=0;i<len-1;i++) {
//数字0~9的ASSIC码值为48~57,大写字母A~Z的ASSIC码值为65~90
//小写字母的ASSIC码值为97-122
if(!((array[i]>=48 && array[i]<=57) ||(array[i]>=65 && array[i]<=90) ||(array[i]>=97 && array[i]<=122))) {
throw new MyStrChkException("字符串:"+str+"中含有非法字符。");
}
}
}
public static void main(String[] args) {
String str1 = "abczA09Z";
String str2 = "ab!334@";
try { chkStr(str1);
System.out.println("字符串1为:"+str1);
chkStr(str2);
System.out.println("字符串1为:"+str2);
}catch(MyStrChkException ex) {
System.out.println("触发自定义的异常,异常内容如下:");
System.out.println(ex.getContent());
}
}
}