25、异常捕获与处理
合理使用异常处理,可以让程序更加健壮。
异常的产生
异常是导致程序中断执行的一种指令流。当异常出现时,如果没有合理处理,程序就会中断执行。
范例:不产生异常的代码
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
System.out.println("2.出发计算:10 / 2 = " + (10 / 2));
System.out.println("3.除法计算结束");
}
}
范例:产生异常
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
// 2.中将出现异常
System.out.println("2.出发计算:10 / 2 = " + (10 / 0));
System.out.println("3.除法计算结束");
}
}
// 结果为:
// 1.除法计算开始
// Exception in thread "main" java.lang.ArithmeticException: / by zero
// at com.java.util.Demo.main(Demo.java:7)
异常产生后,产生异常的语句以及之后的语句将不再执行。默认情况下系统会输出异常信息,而后自动结束程序的执行。
异常的处理
1、Java中进行处理异常,使用try
、catch
、finally
这三个关键字,语法如下:
try {
// 可能出现异常的语句
} catch (异常类型 对象1) {
// 异常处理
} catch (异常类型 对象2) {
// 异常处理
} finally {
// 不论是否出现异常都执行的语句
}
对于上述操作的组合有:try…catch
、try…catch…finally
、try…finally
(这个不建议使用)。
范例:应用异常处理格式
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
System.out.println("2.出发计算:10 / 2 = " + (10 / 0));
} catch (ArithmeticException e) {
}
System.out.println("3.除法计算结束");
}
}
// 结果为:
// 1.除法计算开始
// 3.除法计算结束
2、出现异常就要处理异常,为了能进行异常处理,可以使用异常类中的printStackTrace()
输出完整的异常信息:
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
System.out.println("2.出发计算:10 / 2 = " + (10 / 0));
} catch (ArithmeticException e) {
e.printStackTrace();
}
System.out.println("3.除法计算结束");
}
}
// 结果为:
// 1.除法计算开始
// java.lang.ArithmeticException: / by zero
// at com.java.util.Demo.main(Demo.java:7)
// 3.除法计算结束
范例:使用try…catch…finally
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
System.out.println("2.出发计算:10 / 2 = " + (10 / 0));
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不论是否异常,都执行");
}
System.out.println("3.除法计算结束");
}
}
3、异常捕获时,一个try语句可以跟多个catch语句。
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2.出发计算:" + (x / y));
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不论是否异常,都执行");
}
System.out.println("3.除法计算结束");
}
}
上述程序将由用户输入数据,可能存在以下异常:
· 用户执行时不输入参数(java Demo):java.lang.ArrayIndexOutOfBoundsException
数组越界错误;
· 输入的数据不是数字(java Demo a b):java.lang.NumberFormatException
;
· 被除数为0(java Demo 10 0):java.lang.ArithmeticException
以上代码只有一个catch,只能处理一个异常,其他异常依然会导致程序中断
范例:增加多个catch
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2.出发计算:" + (x / y));
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} finally {
System.out.println("不论是否异常,都执行");
}
System.out.println("3.除法计算结束");
}
}
异常处理流程
1、观察两个异常类的继承结构:
NumberFormatException:
java.lang.Object
|- java.lang.Throwable
|- Exception
|- RuntimeException
|- IllegalArgumentException
|- NumberFormatException
ArithmeticException:
java.lang.Object
|-java.lang.Throwable
|- Exception
|- RuntimeException
|- ArithmeticException
由上表可得,所有异常类都是Throwable
的子类。Throwable下有两个子类Error
和Exception
。
请解释Error和Exception的区别:
· Error:指的是JVM错误,即:此时程序还没有执行,用户不能处理;
· Exception:指的是程序运行中产生的异常,用户可以处理。
所谓的异常处理指的是Exception以及它的子类异常。
2、异常处理流程图
流程描述:
1)当程序运行时出现异常,由JVM自动根据异常类型实例化一个与之类型匹配的异常类对象;
2)产生异常对象后,会判断当前语句是否存在异常处理。如果没有异常处理,就交给JVM进行默认的异常处理(输出异常信息,结束程序调用);
3)如果有异常捕获操作,会由try语句捕获产生的异常类实例化对象,之后与catch语句进行比较,如果有符合的捕获类型,则使用catch语句进行异常处理;如果不匹配,则继续向下匹配其它catch语句;
4)不论异常处理是否能够匹配,都要继续执行,如果程序中存在finally语句,就先执行finally语句中的代码,执行完毕后根据之前catch匹配结果来决定如何执行,如果之前成功捕获异常,那就继续执行finally之后的语句;如果没有成功捕获,就交给JVM进行默认处理。
整个过程和catch中的异常类型进行匹配,但是所有Java对象都可以自动向上转型。即如果真的要匹配类型,简单的做法就是匹配Exception。
public class Demo {
public static void main(String[] args) {
System.out.println("1.除法计算开始");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2.出发计算:" + (x / y));
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("不论是否异常,都执行");
}
System.out.println("3.除法计算结束");
}
}
上述将所有的异常都交由Exception类处理,因此程序无法知道具体产生的是什么异常。
说明:
· 使用多个catch时,范围大的异常一定要放在范围小的异常后面,否则会出现语法错误。
· 直接捕获Exception比较方便,但不合理,因为所有异常都按照同种方式处理。项目中应根据具体异常类型处理。
throws关键字
1、throws关键字主要用于方法声明,将异常交由被调用处(如main方法)处理。
范例:使用throws关键字
class MyMath {
public static int div(int x, int y) throws Exception {
// 使用了throws,所以该方法产生的异常交由调用处处理
return x / y;
}
}
范例:调用上述方法
public class Demo {
public static void main(String[] args) {
// 必须进行异常处理,否则代码报错
try {
System.out.println(MyMath.div(10, 2));
} catch (Exception e) {
e.printStackTrace();
}
}
}
调用了具有throws声明的方法,不论操作是否异常,都需要使用try..catch
进行异常处理。
2.在主方法使用throws关键字后,异常将交给JVM处理,即采用默认处理方式。由于开发的程序多数希望正常结束调用,因此主方法不应该使用throws关键字。
public class Demo {
public static void main(String[] args) throws Exception {
System.out.println(MyMath.div(10, 0));
}
}
throw关键字
1、程序中可以使用throw手工抛出一个异常类的对象。
public class Demo {
public static void main(String[] args) {
try {
throw new Exception("自定义异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
throws与throw的区别:
·throw:在方法中手工抛出一个异常类对象(该对象可以是自定义的,或者已经存在的);
·throws:用于方法声明上,使得调用该方法时必须处理异常。
异常处理标准格式
要求:定义div(),在执行除法前打印提示信息,在计算结束后打印提示信息;如果计算中产生了异常,交给调用处处理。
范例:
class MyMath {
public static void div(int x, int y) {
System.out.println("==== 除法计算开始 ====");
System.out.println(x / y);
System.out.println("==== 除法计算结束 ====");
}
}
public class Demo {
public static void main(String[] args) {
MyMath.div(10,2);
}
}
上述代码可能出现异常,因此要进行异常处理。根据要求,异常交由调用处处理,因此使用throws关键字。
class MyMath {
// 如果div()出现异常,异常交给调用处处理
public static void div(int x, int y) throws Exception {
System.out.println("==== 除法计算开始 ====");
System.out.println(x / y);
System.out.println("==== 除法计算结束 ====");
}
}
public class Demo {
public static void main(String[] args) {
try {
MyMath.div(10, 2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述代码产生错误后,程序运行到System.out.println("==== 除法计算结束 ====");
就不执行了。
范例:正确做法如下:
class MyMath {
// 如果div()出现异常,异常交给调用处处理
public static void div(int x, int y) throws Exception {
System.out.println("==== 除法计算开始 ====");
try {
System.out.println(x / y);
} catch (Exception e) {
throw e; // 抛出异常
} finally {
System.out.println("==== 除法计算结束 ====");
}
}
}
public class Demo {
public static void main(String[] args) {
try {
MyMath.div(10, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
RuntimeException类
范例:观察下述程序
public class Demo {
public static void main(String[] args) {
int temp = Integer.parseInt("100");
}
}
parseInt(): public static int parseInt(String s) throws NumberFormatException;
parseInt()抛出了NumberFormatException,按照之前知识点,此处应强制进行异常捕获,但实际并没有该要求:
观察一下NumberFormatException的继承结构:
java.lang.Object
|- java.lang.Throwable
|- java.lang.Exception
|- java.lang.RuntimeException → 运行时异常
|- java.lang.IllegalArgumentException
|- java.lang.NumberFormatException
Java为方便代码编写,提供了RuntimeException类,该类的特征是:程序在编译时,不会强制性要求用户处理异常,用户可以根据自己的需求选择性处理,但是没有处理又发生异常,就会交给JVM默认处理。
请解释Exception与RuntimeException的区别,请列举常见的几种RuntimeException
· Exception是RuntimeExceptio的父类;
· 使用Exception定义的异常必须要被处理,而RuntimeException的异常可以选择性处理。
·常见的RuntimeException:ArithmeticException、NullPointerException、ClassCastException。
异常的捕获及处理(断言)
assert关键字在JDK1.4引入,其功能是进行断言。
public class Demo {
public static void main(String[] args) {
int num = 10;
assert num == 20 : "num 不等于20";
System.out.println("num = " + num);
}
}
默认情况下,断言是不应该影响程序的运行,即Java在解释程序时,断言是默认不起作用的。
启用断言:java -ea Demo
Exception in thread "main" java.lang.AssertionError: num的内容不是20
at Demo.main(Demo.java:6)
异常的捕获及处理(自定义异常)
- Java自身提供了大量的异常类,但对于实际开发是不够。例如:进行添加数据操作时,可能出现错误数据,错误数据出现就应该抛出异常,例如AddException,而该异常Java没有,需要自己开发。
如果要自己开发一个异常类可以选择继承Exception或RuntimeException。
范例:定义AddException
class AddException extends Exception {
public AddException(String msg) {
super(msg);
}
}
public class Demo {
public static void main(String[] args) {
int num = 11;
try {
if (num > 10) {
throw new AddException("数值传递过大");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述代码,只是介绍自定义异常的形式,不能说明自定义异常的作用。