在实际项目开发过程中,经常会遇到类似这样的问题,例如某程序在编译时没有错误信息产生,但在程序运行时,经常会出现一些运行时的错误,这种错误对Java而言就是一种异常。异常指的是程序在运行时发生的错误或者不正常的状况,有了异常就要有相应的处理方式。异常处理是Java的一个优点,在异常处理中,它的处理机制设计先进,使用方便,不仅提高了程序的健壮性,而且还大大降低了程序员的编程工作量。因此想成功的成为一名合格的Java程序员,掌握好异常处理机制是编写大型程序必不可少的基本功。
常见的异常有哪些
Java中一共提供了try、catch、finally、throw和throws 5个关键字来处理异常,其中的try-catch-finally需要配套使用,它们的作用是捕获和处理异常,使用try关键字和它后面的{}把有可能出现异常的代码包含起来。如果在try语句块中发生异常,这个异常就会被抛出。这时候就可以使用catch语句来捕获异常,并在这个语句块中,对这个异常进行处理。还有一些是不管发不发生异常,都需要执行的代码,就把它们放到finally语句块中。throw关键字用来手动引发一个异常。throws关键字用来定义任何被调用方法的异常。
异常的分类
在Java的lang包里有一个Throwable类,它是所有异常的父类或者间接父类。每个异常类型都是Throwable类的子类,这其中有两个直接子类:Error和Exception。Error类及其子类是用来处理系统内部及程序运行环境的异常,一般与硬件有关,由系统直接处理,不需要程序员在程序中处理。
Exception类是程序能够捕获到的异常情况。它又可以分为两大类:运行时异常(为RuntimeException)和检查型异常(也称非运行时异常)。运行时异常(RuntimeException)是一种设计或者是实现问题上出现的错误,大多数是由于程序设计不当而引发的错误,但这种错误要在运行期间才会发生和被发现。下面列出了系统会出现的运行时异常,如表7.1所示。
表 运行时异常
异常名称 | 异常说明 |
ArithmeticException | 除以0等算术错误 |
ArrayIndexOutOfBoundsException | 数组下标出界 |
ArrayStoreException | 数组元素值与数组类型不同 |
ClassCastException | 强制类型转换异常 |
IllegalArgumentException | 调用方法的参数非法 |
IllegalMonitorStateException | 非法监控操作 |
IllegalStateException | 环境或状态错误 |
IllegalThreadStateException | 请求操作与当前线程不兼容 |
IndexOutOfBoundsException | 索引越界 |
NullPointerException | 非法使用空引用 |
NumberFornatException | 字符串非法转换数字格式 |
SecurityException | 安全性 |
StringIndexOutBounds | 字符串索引越界 |
UnsupportedOperationException | 操作错误 |
除了运行时异常外,其余的异常均为检查型异常,这类异常真正的发生仍然是在运行时,不过编译器在编译时会进行检查,一旦发现某类中的某些语句有可能会产生异常,就提示并强迫开发者立即处理,否则不能通过编译。下面列出了常用的检查异常。如表所示。
检查异常
异常名称 | 异常说明 |
ClassNotFoundException | 找不到相关类 |
CloneNotSupportedException | 对象不能实现 |
IllegalAccessException | 访问类被拒绝 |
InstantiationException | 创建抽象对象 |
InsterruptedException | 线程被另一个线程中断 |
NotSuchFieldException | 请求的内容不存在 |
NoSuchMethodException | 请求的方法不存在 |
【代码剖析】
(1)这是一个关于运行时异常的例子,具体代码如下:
public class Abnormal {
public static void main(String[] args) {
int arry[] = { 0, 1, 3, 4, 6 };
for (int i = 0; i < 10; i++) {
System.out.println(arry[i]);
}
}
}
运行结果如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Abnormal.main(Abnormal.java:7)
由于arry[]的长度为5,而在输出时候要求打印出10个元素,因此就会出现“rrayIndexOutOfBoundsException”,也数组下标出界异常,但是在整个编译的过程中,因为没有语法上的错误,所以编译通过。
异常的处理方式之一:try/catch
在本小节开始,将要依次讲解try/catch捕获异常、throws声明异常、throw抛出异常等诸多内容。在出现异常后,可以直接捕获处理它,也可以先对它不处理而是把它抛给上面的调用者。在本小节中主要讲解try/catch捕获异常的方式
try/catch捕获异常的实例
Java程序语言对于异常的处理,通过try和catch语句来实现。当有一个异常被抛到异常处理器后,通过try和catch语句对被抛出的异常进行捕获和处理。其语法结构如下:
try{
语句1;
语句2; …….
}catch(异常类型A 变量名){
语句3;
}catch(异常类型B 变量名){
语句4;
}
程序中的其他语句5;
【代码剖析】先看一个代码示例,从中总结出try/catch捕获异常有哪些需要值得注意的地方,具体代码如下:
1 public class Abnormal {
2 public static void main(String[] args) {
3 int arry[] = { 0, 1, 3, 4, 6 };
4 int a = 0;
5 try {
6 int z = 5 / a;
7 for (int i = 0; i < 10; i++) {
8 System.out.println(arry[i]);
9 }
10 } catch (ArithmeticException e) {
11 System.out.println("0不能作为被除数");
12 } catch (ArrayIndexOutOfBoundsException e) {
13 System.out.println("arry[]的长度是5,数组下标越界了");
14 }
15 System.out.println("没有异常运行成功了");
16 }
17 }
运行结果如下所示:
0不能作为被除数
没有异常运行成功了
在上面这段代码中,6和8行都会有异常发生,从上面的运行结果中可以看出,如果在6处发生了异常,且异常类型是ArithmeticException,那么代码的执行顺序为:6-11-15。如果6处不发生异常,那么运行结果又会是什么呢?如下所示:
1 public class Abnormal1 {
2 public static void main(String[] args) {
3 int arry[] = { 0, 1, 3, 4, 6 };
4 int a = 2;
5 try {
6 int z = 5 / a;
7 System.out.println("z="+z);
8 for (int i = 0; i < 10; i++) {
9 System.out.println("arr["+i+"]="+arry[i]);
10 }
11 } catch (ArithmeticException e) {
12 System.out.println("0不能作为被除数");
13 } catch (ArrayIndexOutOfBoundsException e) {
14 System.out.println("arry[]的长度是5,数组下标越界了");
15 }
16 System.out.println("没有异常运行成功了");
17 }
18 }
运行结果如下所示:
z=2
arr[0]=0
arr[1]=1
arr[2]=3
arr[3]=4
arr[4]=6
arry[]的长度是5,数组下标越界了
没有异常运行成功了
在上面这段代码中,7行正常,9行有异常发生,从上面的运行结果中可以看出,如果在9处发生了异常,且异常类型是ArrayIndexOutOfBoundsException,那么代码的执行顺序为:7-9-14-16。如果某处发生异常,且异常类型即不是ArithmeticException也不是ArrayIndexOutOfBoundsException,那么运行结果又会是什么呢?如下所示:
1 public class Abnormal2 {
2 public static void main(String[] args) {
3 int a = 2;
4 String b[]=new String[2];
5 try {
6 int z = 5 / a;
7 System.out.println("z="+z);
8 b[0].toLowerCase();
9 } catch (ArithmeticException e) {
10 System.out.println("0不能作为被除数");
11 } catch (ArrayIndexOutOfBoundsException e) {
12 System.out.println("arry[]的长度是5,数组下标越界了");
13 }
14 System.out.println("没有异常运行成功了");
15 }
16 }
运行结果如下所示:
z=2
Exception in thread "main" java.lang.NullPointerException
at Abnormal.main(Abnormal.java:8)
在上面这段代码中,7处正常,在8处发生了异常,并且异常类型不是catch语句中的任何一种类型,从上面的运行结果中可以看出,代码的执行顺序为:7-8-系统处理。
【解释说明】综合上述执行过程可以总结如下: 当try中的某条语句引发了异常后,程序立即跳转到catch部分,查找和该异常类型相匹配的catch语句并执行catch{}中的所有语句,而位于try中的发生异常的语句后面所有的语句(正常语句和会发生异常的语句)都不会执行。
这里还需要提到的是finally语句。无论是否有匹配的catch语句,也无论是否发生了异常,程序最终都会转到finally中来执行。finally很简单,这里就不举代码演示了。只要记住它和try/catch配套使用就可以了。
异常的处理方式之二:throws
在本小节中主要讲解throws声明异常的方式。异常的声明是告诉Java编译器有一个异常出现了。
在方法中,异常声明在方法的头部,利用关键字“throws”来表示此方法在运行的时候,很可能会出现异常现象。
throws声明异常的实例
声明异常是指一个方法不处理它产生的异常,而是向上传递,谁调用这个方法,这个异常就由谁处理。例如在某方法内的某一段代码可能会发生异常,但是在这个方法内不想任何的异常处理,所以在没有使用任何的代码块来捕捉这些异常时,那么就必须在声明方法时同时指出所有可能发生的异常,给以后要调用此方法的其他方法提个醒,意思是我这有个xx异常等着你来处理呢,你要做好准备啊。声明异常使用到了throws关键字,在方法名和throws关键字后加上抛出的异常,基本格式如下:
方法返回值类型 方法名(参数列表)throws 异常类1,异常类2,…
【代码剖析】这是一个关于由一个方法抛出异常由调用该方法的上级方法处理的实例,具体代码如下:
public class AbnormalTest{
// 声明异常
public void catchThows(int str) throws ArrayIndexOutOfBoundsException,
ArithmeticException, NullPointerException { //声明catchThows方法的同时指出要可以出现的异常类型
System.out.println(str);
if (str == 1) {
int[] a = new int[3];
a[5] = 5;
} else if (str == 2) {
int i = 0;
int j = 5 / i;
} else if (str == 3) {
String s[] = new String[5];
s[0].toLowerCase();
} else {
System.out.println("正常运行,没有发现异常");
}
}
public static void main(String args[]) {
AbnormalTest yc = new AbnormalTest ();
try {
yc.catchThows(0);
} catch (Exception e) {
System.out.println("异常:" + e); //捕获Exception异常,并打印出相应的异常信息
}
try {
yc.catchThows(1);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("异常:" + e); //捕获ArrayIndexOutOfBoundsException异常,并打印出相应的异常信息
}
try {
yc.catchThows(2);
} catch (ArithmeticException e) {
System.out.println("异常:" + e); //捕获ArithmeticException异常,并打印出相应的异常信息
}
try {
yc.catchThows(3);
} catch (Exception e) {
System.out.println("异常:" + e); //捕获Exception异常,并打印出相应的异常信息
}
}
}
运行结果如下:
0
正常运行,没有发现异常
1
异常:java.lang.ArrayIndexOutOfBoundsException: 5
2
异常:java.lang.ArithmeticException: / by zero
3
异常:java.lang.NullPointerException
【解释说明】在本程序中,需要注意的是在方法中必须要声明可能发生的所有异常,同时在调用该方法的main方法中定义try/catch语句来捕获该异常。
异常的处理方式之三:throw
异常的抛出,就是将异常抛给异常处理器,暂时不去处理它。在本小节中主要讲解用throw抛出异常的方式,以及如何由try-catch来接收所抛出的异常。
throw抛出异常的实例
当一个方法发生异常时可以通过throw关键字来抛出异常,把异常抛给它上一级的调用者,抛出的可以是异常引用,也可以是异常对象,它的语法格式如下:
throw 异常对象名;
或者
throw new 异常类名();
一条throw语句一旦被执行,程序立即转入相应的异常处理程序段,它后面的语句就不再执行了(这一点类似于return语句),而且它所在的方法也不再返回有意义的值。一个方法中,throw语句可以有多条,但每一次最多只能执行其中的一条。一般情况下,throw语句都会写在判断语句块中,以避免每次都执行该语句。
【代码剖析】下面来看一个例子,也许从中你会明白点什么?具体代码如下:
public class catchThows {
static int x;
public static void main(String argv[]) {
double a = Math.random() * 10;
if (x > 0)
System.out.println(a / x);
else
throw new Exception(); // 抛出异常
}
public void setX(int x) {
this.x = x;
}
}
运行结果如图7.1所示。
图7.1 运行结果
从上面的运行结果中可以看出,一个方法中如果使用throw来抛出异常,要么自己捕获它,要么声明抛出了一个异常。要声明抛出了异常,需要用throws关键字在方法的头部声明。如果我们将上面的代码修改成下面的代码,那么结果又会怎样呢?代码如下:
public class catchThows_1 {
static int x;
public static void main(String argv[]) {
new catchThows().setX(0);
double a = Math.random() * 10;
if (x > 0)
System.out.println(a / x);
else
try {
throw new Exception();// 抛出异常
} catch (Exception e) {
System.out.println("出现异常的原因是:"+e.getMessage());
}
}
public void setX(int x) {
this.x = x;
}
}
运行结果如下:
出现异常的原因是:null
还有一种抛出异常的方法是让被调用的方法表示它将不处理异常,该异常将被抛出到它的调用方法中。这点是和throws声明异常很相似的,但它们的位置是完全不同的。具体代码如下:
public class catchThows_2
{
void findThows()
{
try
{
//抛出给方法
throw new ArithmeticException();
}
catch(ArithmeticException ae)
{
throw ae;
}
}
public static void main(String args[])
{
catchThows ct=new catchThows();
//对方法进行异常处理
try
{
ct.findThows();
}
catch(ArithmeticException ae)
{
System.out.println("出现异常的原因是:"+ae);
}
}
}
运行结果如下:
出现异常的原因是:java.lang.ArithmeticException