目录
什么是异常
异常是导致程序中断执行的一组指令流。
来看一个典型的异常:
public class class1 {
public static void main(String[] args) {
System.out.println("1");
System.out.println(1 / 0);
System.out.println("2");
}
}
这是一个典型的除数为零的异常。当程序执行到第二句时,由于除数是零,程序会发生错误,此时便会产生一个中断,中断之后语句的执行,抛出异常信息并将程序结束。
那么这一段代码的输出就是这样的:
在输出语句1时,由于此时还没有产生异常,因此是可以正常输出的 ;在输出语句2时,此时产生了异常,因此这一句输出语句不会正常输出,并且会抛出一个异常信息,中断之后代码段的执行,因此输出语句3也不会正常输出。
既然异常会导致程序的中断执行,那么是否存在一种处理方式,使得程序在出现了非致命的错误时,还可以正常的结束,而不是突然抛出一个异常并且异常之后的语句都不执行。
此时便会涉及到异常的处理。
异常的处理
在java中,若是要对异常进行处理,可以使用try、catch、finally这几个关键字来完成。
try修饰的代码块中包含的是可能出现异常的语句;
catch(异常的类型 异常对象)修饰的代码块中包含的是对异常的处理;
finally修饰的代码块中包含的是异常处理完/没有发生异常时执行的语句。也就是说,无论是否发生异常,finally所包含的代码块中的语句始终是会被执行的。
当然了,在这三个关键字中,try修饰的代码块是不可或缺的,而catch修饰的代码块和finally修饰的代码块是可以自由搭配使用的,并且catch修饰的代码块可以有多个,但finally修饰的代码块最多只有一个。
例如这是一个try...catch组合的异常处理代码段:
public class class1 {
public static void main(String[] args) {
try {
//可能出现异常的语句
} catch (Exception e) {
//对异常的处理
}
}
}
例如这是一个try...catch...catch组合的异常处理代码段:
public class class1 {
public static void main(String[] args) {
try {
//可能出现异常的语句
} catch (异常类型 e1) {
//对异常的处理
} catch (异常类型 e2) {
}
}
}
例如这是一个try...catch...finally组合的异常处理代码段:
public class class1 {
public static void main(String[] args) {
try {
//可能出现异常的语句
} catch (Exception e) {
//对异常的处理
} finally {
//异常处理结束或者从未发生异常时执行的语句
}
}
}
当然,也可以在异常发生时不对异常进行任何处理,例如这是一个try...finally组合的异常处理代码段:
public class class1 {
public static void main(String[] args) {
try {
//可能出现异常的语句
} finally {
//异常处理结束或者从未发生异常时执行的语句
}
}
}
下面来实际处理一个异常:
public class class1 {
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (Exception e) {
System.out.println("异常信息为: " + e);
}
System.out.println("program end");
}
}
程序的打印结果为:
但是,这样直接打印异常信息得到的异常信息是不完整的,它只会证明出现了这个异常,而无法告知程序员这个异常出现的位置。
此时便可以使用异常类中提供的printStackTrace()方法来打印完整的异常信息,例如:
public class class1 {
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (Exception e) {
System.out.println("异常信息为: " + e);
e.printStackTrace();
}
System.out.println("program end");
}
}
此时程序的打印结果为:
tip:虽然此处也出现了和没有进行异常处理时一样的红色异常信息,但此处是由于调用了printStackTrace()方法而打印的异常信息,程序是正常执行结束的。可以看到异常语句之后的输出语句可以正常输出并且进程的退出代码是0,这就说明异常得到了处理,程序是正常结束的。
当然,也可以同时处理多个可能的异常:
public class class1 {
public static void main(String[] args) {
try {
int num1 = Integer.parseInt(args[0]);
int num2 = Integer.parseInt(args[1]);
System.out.println(num1 / num2);
} catch (ArrayIndexOutOfBoundsException e1) {
System.out.println("数组索引越界");
} catch (ArithmeticException e2) {
System.out.println("算术错误,除数为0");
}
System.out.println("program end");
}
}
此处可能会出现两个异常:
- 因为是使用数组的形式给程序传递实参,因此若是数组中一个元素都没有或者只有一个元素,那么在获取两个数组元素时便会发生异常,此时数组的索引会越界。
- 因为计算的是一个除法,并且被除数和除数的数值是不定的,因此可能会出现除数为0的异常。
这样以来,无论实际发生的是上面两种异常中的哪一种,异常都可以得到正确处理。
异常的类型
所有异常的父类都是Exception类,打开Exception类,查看该类的层次结构便可以发现该类所包含的异常类型:
例如上面出现的ArrayIndexOutOfBoundsException错误类型便表示数组索引越界、ArithmeticException错误类型便表示算术错误。
异常的处理流程
throws
如果定义了一个方法,那么此时应该明确地告诉使用者,这个方法是否会产生异常以及当产生异常时,可能会产生什么样的异常。
此时便可以在声明方法时,使用throws关键字进行异常类型的标注,例如这样:
public class class1{
void fun1() throws ArithmeticException { //可以抛出一个具体的异常类型
}
void fun2() throws Exception{ //也可以偷懒直接抛出异常的父类
}
}
此时会将出现的异常抛给调用者进行处理,如果调用者无法处理,那么调用者还可以将这个异常再向上抛出,例如main()方法虽然作为程序的入口,但它也是一个方法,因此main()也可以向上抛出异常,此时抛出的异常表示程序本身无法进行处理,该异常会交由JVM进行处理。
throw
throw和throws很相似,都是向上抛出一个异常,但是throws是在方法声明时使用的,throw是在执行语句时手动地将异常抛出,例如这样:
public class class1 {
public static void main(String[] args) {
try {
fun1();
} catch (Exception e) {
System.out.println(e);
}
System.out.println("program end");
}
static void fun1() throws Exception {
try {
System.out.println(1 / 0);
} catch (Exception e) {
throw new Exception("除数为0");//当然这里也可以实例化Exception类的子类
}
}
}
此时打印的信息为:
一个异常处理的栗子
如果在方法定义时使用了throws向上抛出异常,那么在方法体中,便可以不存在catch语句,单纯使用try...finally的组合处理异常,当出现异常时,该异常会自动向上抛出,就像这样:
public class class1 {
public static void main(String[] args) {
try {
fun1();
} catch (Exception e) {
System.out.println(e);
}
System.out.println("program end");
}
static void fun1() throws Exception {
try {
System.out.println(1 / 0);
} finally {
}
}
}
程序的运行结果为:
当然了,可以存在多组try...finally的组合,并且无论存在多少组try...finally的组合,该方法只会向上抛出零个或者一个异常,因为程序执行到第一个确实发生异常的地方便会中断该方法,向上抛出异常,就像这样:
public class class1 {
public static void main(String[] args) {
try {
fun1();
} catch (Exception e) {
System.out.println(e);
}
System.out.println("program end");
}
static void fun1() throws Exception {
try {
System.out.println(1 / 0);
} finally {
}
try {
int[] ints = new int[1];
ints[2] = 1;
} finally {
}
System.out.println("1");
}
}
fun1()中的两段代码都是存在异常的,但是整个fun1()方法只会向上抛出除数为零的异常,而不会向上抛出数组索引越界的异常,因为当产生第一个异常时,这个方法接下来的语句都不会执行了。
程序的执行结果如下:
可以发现,fun1()中的打印语句并没有被执行,因此是发生异常时就终止该方法并向上抛出异常。
RuntimeException
该类是Exception类的子类;
该类中所包含的异常可以在出现时不需要强制进行处理。
自定义异常
继承Exception类的自定义异常
例如此时有一个自定义的异常类myException,继承自Exception类:
public class myException extends Exception {
public myException(String message) {
super(message);
}
}
此时便可以使用自定义的异常输出异常信息,而不用记住所有的自带异常信息:
public class class3{
static void eat(int num) throws myException{
if(num>20){
throw new myException("eat too much");
}else{
System.out.println("eating");
}
}
public static void main(String[] args) throws myException {
eat(10);
eat(20);
eat(30);
}
}
程序的运行结果为:
可以看到,可以正常抛出自定义异常信息。
继承RuntimeException类的自定义异常
其他代码都不变,将自定义异常类myException改为继承自RuntimeException类:
public class myException extends RuntimeException {
public myException(String message) {
super(message);
}
}
程序的运行结果为:
可以看到,程序是可以正常抛出自定义的异常信息。