异常处理
参考资料:《Java从入门到精通》/明日科技编著. 4版. 北京:清华大学出版社,2016
一、异常概述
异常是一个在程序执行过程中发生的事件,它中断了正在执行程序的正常指令流。有很多异常的例子,如空指针、数组溢出等。
Java是一门面向对象的编程语言,因此,异常在Java语言中也是作为类的实例的形式出现的。当某一方法发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统,这个对象就是异常对象。
通过异常处理机制,可以将非正常情况下的处理代码与程序的主逻辑分离,即在编写代码主流程的同时,在其他地方处理异常。
二、处理程序异常错误
为了保证程序的正常执行,需要对发生的异常进行相应的处理。
在Java中,如果某个方法抛出异常,既可以在当前方法中进捕捉,然后进行处理,也可以将异常向上抛出,由方法调用者来处理。
下面介绍Java中捕捉异常的方法:
1、错误
异常产生后,如果不做任何处理,程序就会被终止。
public class Demo1 {
public static void main(String[] args) {
String str1 = "23";
String str2 = "ab";
int age1 = Integer.parseInt(str1); //将数字型字符串转换成int型
System.out.println(str1);
int age2 = Integer.parseInt(str2); //将数字型字符串转换成int型
System.out.println(str2);
}
}
运行结果:
23
Exception in thread "main" java.lang.NumberFormatException: For input string: "ab"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.demo_5_16.Demo1.main(Demo1.java:10)
从上述代码的执行结果可以看出,报出了NumberFormatException(字符串转数字)的异常,str1变量的值已经输出,str2变量的值没有输出,可知程序由于异常而终止。
2、捕捉异常
Java的异常捕捉结构由try、catch和finally组成;
try语句块存放的是可能发生异常的Java语句;
catch语句在try语句块之后,用来激发被捕获的异常;
finallly语句块是异常处理结构的最后执行部分,无论try语句块的代码如何退出,都将执行finally语句块。
语法如下:
try {
//代码块
}
catch (ExceptionType1 e) {
//对 ExceptionType1的处理
}
catch (ExceptionType2 e) {
//对 ExceptionType2的处理
}
...
...
finally {
//代码块
}
由此可知,异常处理器大致分为try-catch语句块和finally语句块。
(1)try-catch语句块
public class Demo1 {
public static void main(String[] args) {
try {
String str1 = "23";
String str2 = "ab";
int age1 = Integer.parseInt(str1); //字符型转换成int型
System.out.println(str1);
int age2 = Integer.parseInt(str2); //字符型转换成int型
System.out.println(str2);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("程序结束");
}
}
运行结果:
23
程序结束
java.lang.NumberFormatException: For input string: "ab"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.demo_5_16.Demo1.main(Demo1.java:12)
从上述代码的运行结果可知,程序输出了最后的提示信息,没有因为异常而终止。
使用try-catch语句块进行异常处理过程:
当try语句块中的语句发生异常时,程序就会调转到catch代码块中执行;
执行完catch代码后,将继续执行catch代码块后面的代码,而不会执行try代码块中发生异常语句后面的代码。
其中,Exception是代码块传递给catch代码块的变量类型,e是变量名。通常,异常处理常用的3个函数来获取异常的有关信息。
getMessage() :输出错误的性质;
toString() :给出异常的类型与性质;
printStackTrace() :指出异常的类型、性质、栈层次以及出现在程序中的位置。
(2)finally语句块
完整的异常处理语句一定要包括finally语句块,无论程序中有无异常的发生,并且无论之前的try-catch语句是否顺利执行完毕,都会执行finally语句。
在4种情况下finally语句不会执行:
在finally语句块中出现了异常;
在前面的代码中使用了System.exit()退出程序;
程序所在的线程死亡;
关闭CPU。
四、自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户只需要继承Exception类即可自定义异常类。
在程序中使用自定义异常类,大可分为以下几个步骤:
创建自定义异常类;
在方法中通过throw关键字抛出异常对象;
如果在当前抛出异常的方法中处理异常,可以使用try-catch语句块捕捉异常并处理,否则在方法的声明处通过throw关键字指明要抛出给方法调用者的异常,继续进行下一步;
在出现异常方法的调用者中捕获并处理异常。
/*
*通过继承Exception类,自定义一个异常类
*/
class MyException extends Exception {
//自定义异常类的构造方法
public MyException (String ErrorMessage){
//调用父类的构造方法
super(ErrorMessage);
}
}
public class Tran {
//定义方法,并使用throws关键字指明要抛出的异常的类型,即异常类型为MyException类
static int avg(int n1, int n2) throws MyException {
//判断参数是否满足指定的条件
if (n1 < 0 || n2 < 0){
//抛出异常对象
throw new MyException("不可以为负数");
}
//判断参数是否满足指定的条件
if (n1 > 100 || n2 > 100){
//抛出异常对象
throw new MyException("数字过大");
}
//若参数正常,则返回结果
return (n1 + n2) / 2;
}
public static void main(String[] args) {
//使用try-catch语句块捕捉异常
try{
int result = Tran.avg(112,-34); //调用avg()静态方法
System.out.println(result);
}catch (MyException e){ //处理捕获到的异常,相当于 MyException e = new MyException("不可以为负数");
//当然,这里写成"Exception e"也是没有问题的,因为MyException继承了Exception类
System.out.println(e); //输出异常信息
// e.printStackTrace(); //也可以使用该语句打印异常信息
}
}
}
运行结果:
com.demo_5_17.MyException: 不可以为负数
五、在方法中抛出异常
若某个方法中可能会发生异常,但是不想在当前方法中处理异常,则可以使用throws、throw关键字在方法中将异常抛出。
1、使用throw关键字抛出异常
throws关键字通常用在声明方法时,用来指定方法可能抛出的异常,多个异常可以用逗号分隔。
public class Shoot {
//定义方法,并指定抛出异常的类型
public void pop() throws NegativeArraySizeException {
int arr[] = new int[-3]; //故意制造一个异常,即指定数组长度为-3
}
public static void main(String[] args) {
try {
Shoot shoot = new Shoot();
shoot.pop();
}catch (NegativeArraySizeException e){
System.out.println("异常类型为:" + e);
}
}
}
运行结果:
异常类型为:java.lang.NegativeArraySizeException
上述代码中,使用throws关键字将异常抛给方法的调用者。使用throw关键字将异常抛给上一级后,如果不想处理该异常,可以继续往上抛,但最终要有能处理该异常的代码。
2、使用throw关键字抛出异常
throw关键字通常用于方法体中,并且抛出一组异常对象;
程序在执行到throw语句时立即终止,它后面的语句都不执行;
通过throw抛出异常后,如果想在上一级代码中捕获并处理异常,则需要在抛出异常的方法中使用throws关键字在方法声明时指明要抛出的异常。
如果要捕获throw抛出的异常,则必须使用try-catch语句。
throw通常用来抛出用户自定义的异常。
/*
*自定义异常类
*/
class MyException2 extends Exception {
String message;
public MyException2(String message){
this.message = message;
}
public String getMessage(){
return this.message;
}
}
public class Captor {
/*
* 在声明方法时,指定抛出异常类型
* 可以指定多个可能抛出的异常类型
*/
public int div(int n1, int n2) throws MyException2, ArithmeticException{
if (n2 < 0){
throw new MyException2("除数不能是负数"); //抛出异常对象
}
if (n2 == 0){
throw new ArithmeticException(); //抛出异常对象
}
return n1 / n2;
}
public static void main(String[] args) {
try{
Captor captor = new Captor();
int result = captor.div(24,-3);
}catch (MyException2 e){
//若抛出的异常对象为MyException2类型,则执行该代码块
System.out.println(e.getMessage());
}catch (ArithmeticException e){
//若抛出的异常对象为ArithmeticException类型,则执行该代码块
System.out.println("抛出算术异常");
}catch (Exception e) {
//如果还有其他类型,则使用Exception来捕捉
System.out.println("抛出其他异常");
}
}
}
运行结果:
除数不能是负数
上述代码中使用了多个catch语句进行捕捉异常:
如果调用div(24, -3)方法,将发生MyException异常,程序调转到catch (MyException2 e)代码块中执行。
如果调用div(24, 0)方法,将发生ArithmeticException异常,程序调转到catch (ArithmeticException e)代码块中执行。
如果还有其他类型异常发生,则使用catch (Exception e)捕捉异常。
由于Exception是所有异常类的父类,如果将catch (Exception e)代码块放置在其他两个代码块的前面,后面的代码块将永远得不到执行,也就没有什么意义了,因此catch语句的顺序不可随意调换。
六、运行时异常
RuntimeException异常是程序运行过程中产生的异常。
Java类库的每个包中都定义了异常类,所有的这些类都是Throwable类的子类。
Throwable类派生出了两个子类,分别是Exception类和Error类。
Error类及其子类用来描述Java运行系统中的内部错误以及资源耗尽的错误,这类错误比较严重。
Exception类称为非致命性错误类,可以通过捕捉处理使程序继续执行。
Exception类又根据错误发生的原因分为RuntimeException异常和非RuntimeException异常。
Java中提供了常见的RuntimeException异常,这些异常可以通过try-catch语句捕获,如下表所示:
种类
说明
NullPointerException
空指针异常
ArrayIndexOutOfBoundsException
数组下标越界异常
ArithmeticException
算术异常
ArrayStoreException
数组中包含不兼容的值抛出的异常
IllegalArgumentException
非法参数异常
SecurityException
安全性异常
NegativeArraySizeException
数组长度为负数异常
七、异常的使用原则
Java异常强制用户去考虑程序的健壮性和安全性。
异常的处理不应该用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应的处理。
编写代码处理某个方法可能出现的异常时,可遵守以下几条原则:
在当前方法声明中使用try-catch语句捕捉异常;
一个方法被覆盖时,覆盖它的方法必须抛出相同的异常或该异常的子类;
如果父类抛出多个异常,则覆盖方法必须抛出那些异常的一个子集,不能抛出新异常。